不看不知道,一看吓一跳,已经整整一个月没有更新 underscore 源码解读系列文章了。前面我们已经完成了 Object ,Array,Collection 上的扩展方法的源码剖析,本文开始来解读 Function 上的扩展方法。

完整的 underscore 源码解读系列文章请移步 https://github.com/hanzichi/underscore-analysis,觉得还阔以的话,给个 star 鼓励下楼主呗 ^_^

bind 简介

Ok,今天要讲的正是 bind。关于 bind,可以先移步楼主以前写的文章 ECMAScript 5(ES5) 中 bind 方法简介备忘,有个基本的概念。

bind() 方法会创建一个新函数,当这个新函数被调用时,它的 this 值是传递给 bind() 的第一个参数, 它的参数是 bind() 的其他参数和其原本的参数。

语法是这样样子的:

fun.bind(thisArg[, arg1[, arg2[, ...]]])
  • thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
  • arg1, arg2, ... (可选)当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数。

参数

bind 的第一个参数会作为原函数运行时的 this 指向,不多说;而第二个开始的参数是可选的,当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数。怎么理解?

function fn(a, b, c) {
  return a + b + c;
}

var _fn = fn.bind(null, 10);
var ans = _fn(20, 30); // 60

fn 函数需要三个参数,_fn 函数将 10 作为默认的第一个参数,所以只需要传入两个参数即可,如果你不小心传入了三个参数,放心,也只会取前两个。

function fn(a, b, c) {
  return a + b + c;
}

var _fn = fn.bind(null, 10);
var ans = _fn(20, 30, 40); // 60

这有啥用呢?如果某些函数,前几个参数已经 “内定” 了,我们便可以用 bind 返回一个新的函数。也就是说,bind() 能使一个函数拥有预设的初始参数。这些参数(如果有的话)作为 bind() 的第二个参数跟在 this 后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

new

使用 bind 返回的结果还是个 function,是个 function 就可以被 new 运算符调用,那么结果呢?规范中说的很清楚了,当使用 new 操作符调用绑定函数时,bind 的第一个参数无效。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

var _Person = Person.bind({});
var p = new _Person('hanzichi', 30); // Person {name: "hanzichi", age: 30}

一般我们不会去这么用,但是如果要写个 bind 的 polyfill(http://caniuse.com/#search=bind),还是需要考虑用 new 调用的情况。

我们也可以设置默认值(参考上一小节),原先提供的那些参数仍然会被前置到构造函数调用的前面。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

var _Person = Person.bind(null, 'hanzichi');
var p = new _Person(30); // Person {name: "hanzichi", age: 30}

配合 setTimeout

什么时候容易丢失 this 指向?恩,setTimeout 是一个场景,很容易把 this 指向 window,当然,setInterval 也是一样。当使用对象的方法时,需要 this 引用对象,你可能需要显式地把 this 绑定到回调函数以便继续使用对象。

var canvas = {
  render: function() {
    this.update();
    this.draw();
  },

  update: function() {
    // ...
  },

  draw: function() {
    // ...
  }
};

window.setInterval(canvas.render, 1000 / 60);

用 canvas 写特效或者做游戏时经常会碰到类似的问题。上面的代码是有问题的,render 方法中的 this 其实被指向了 window!我们可以用 bind,显式地把 this 绑定到回调函数以便继续使用该对象。

window.setInterval(canvas.render.bind(canvas), 1000);

类似的情况还有 dom 的事件监听,一不小心可能 this 就被指向了 dom 元素。可以参考下以前做 bigrender 时写的这部分代码 https://github.com/hanzichi/hanzichi.github.io/blob/master/2016/bigrender/js/bigrender.js#L179-L184

tip

bind 还能做一些有意思的事情。

通常来说,将一个类数组转为数组,我们会用 slice(ie9- 不支持)。参考 https://github.com/hanzichi/underscore-analysis/issues/14

var slice = Array.prototype.slice;

// slice.apply(arguments);
// slice.call(arguments, 1);

bind 能让调用变的更加简单。

// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);

// ...

slice(arguments);
// slice(arguments, 1);

再举个类似的例子,比如说我们要添加事件到多个节点,for 循环当然没有任何问题,我们还可以 “剽窃” forEach 方法:

Array.prototype.forEach.call(document.querySelectorAll('input[type="button"]'), function(el){
  el.addEventListener('click', fn);
});

更进一步,我们可以用 bind 将函数封装的更好:

var unboundForEach = Array.prototype.forEach
  , forEach = Function.prototype.call.bind(unboundForEach);

forEach(document.querySelectorAll('input[type="button"]'), function (el) {
  el.addEventListener('click', fn);
});

同样类似的,我们可以将 x.y(z) 变成 y(x,z) 的形式:

var obj = {
  num: 10,
  getCount: function() {
    return this.num;
  }
};

var unboundBind = Function.prototype.bind
  , bind = Function.prototype.call.bind(unboundBind);

var getCount = bind(obj.getCount, obj);
console.log(getCount());  // 10

再举个栗子。每隔一秒在控制台打印 1-5,看起来是道考察闭包的经典题目。

for(var i = 1; i <= 5; i++) {
  !function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  }(i);
}

ES6 下能用 let

for(let i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
}

也可以用 bind,瞬间逼格提升:

for(var i = 1; i <= 5; i++) {
  setTimeout(console.log.bind(console, i), i * 1000);
}

2016-08-30 add:好吧,setTimeout 还能传入第三个参数,又涨姿势了 ...

for (var i = 1; i <= 5; i++) {
  setTimeout(function(num) {
    console.log(num)
  }, i * 1000, i);
}

Read more

关于 bind 的介绍就到这里,下一篇文章将结合 underscore 来讲讲如何实现一个 bind 的 polyfill。

关于 bind 你可能需要了解的知识点以及使用场景的更多相关文章

  1. Underscore _.template 方法使用详解

    为什么用「void 0」代替「undefined」 undefined 并不是保留词(reserved word),它只是全局对象的一个属性,在低版本 IE 中能被重写. 事实上,undefined ...

  2. 优雅手撕bind函数(面试官常问)

    优雅手撕bind函数 前言: 为什么面试官总爱让实现一个bind函数? 他想从bind中知道些什么? 一个小小的bind里面内有玄机? 今天来刨析一下实现一个bind要懂多少相关知识点,也方便我们将零 ...

  3. js里function的apply vs. bind vs. call

    js里除了直接调用obj.func()之外,还提供了另外3种调用方式:apply.bind.call,都在function的原型里.这3种方法的异同在stackoverflow的这个答案里说的最清楚, ...

  4. docker 系列 - Dock高阶知识点文章汇集

    docker 技术已经研究了一段时间, 基本的知识点和使用场景比较清楚了, 还有一些比较高阶的内容未曾尝试, 将来工作中如果能用到, 再深入研究吧. 这篇博文汇总了一些有用的文章链接. ======= ...

  5. 第 6 章 存储 - 039 - Data Volume 之 bind mount

    Data Volume Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统中. Data Volume 有以下特点: 1.Dat ...

  6. bind函数作用、应用场景以及模拟实现

    bind函数 bind 函数挂在 Function 的原型上 Function.prototype.bind 创建的函数都可以直接调用 bind,使用: function func(){ consol ...

  7. 面试官:能解释一下javascript中bind、apply和call这三个函数的用法吗

    一.前言    不知道大家还记不记得前几篇的文章:<面试官:能解释一下javascript中的this吗> 那今天这篇文章虽然是介绍javascript中bind.apply和call函数 ...

  8. 【数据结构】什么是AVL树

    目录 什么是AVL树 1. 什么是AVL树 2. 节点的实现 3. AVL树的调整 3.1 LL旋转 3.2 RR旋转 3.3 RL旋转 3.4 LR旋转 什么是AVL树 二叉查找树的一个局限性就是有 ...

  9. 精通Web Analytics 2.0 (4) 第二章:选择你的网络分析灵魂伴侣的最佳策略

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第二章:选择你的网络分析灵魂伴侣的最佳策略 在Web Analytics 2.0的新世界秩序中,您必须跳出"单一真理来 ...

随机推荐

  1. Win10 连接L2TP VPN 失败解决方法

    Win10 连接L2TP VPN 失败解决方法 iOS系统不知道在什么时候,已经不支持PPTP VPN.偶尔的机会刚好看到github上的一键式VPN服务器部署脚本setup-ipsec-vpn,就在 ...

  2. SQL Server 2012 清理日志 截断日志的方法

    MEDIA数据库名 ALTER DATABASE MEDIA SET RECOVERY SIMPLE WITH NO_WAIT ALTER DATABASE MEDIA SET RECOVERY SI ...

  3. 安装MySql for Visual Studio的坑

    阅读目录 问题描述 解决过程 解决方案 总结 回到顶部 问题描述 安装MySql for Visual Studio 一般来说是为了能在VS的服务器数据连接的数据源中能选择MySql类型,如下图: 但 ...

  4. HTML5新增及移除的元素

    HTML经过10多年的发展,其元素经历了废弃与不断重新定义的过程.为了更好的处理现在的互联网应用,HTML5新增了图形绘制.多媒体播放.页面结构.应用程序存储.网络工作等新元素.http://hove ...

  5. 【无私分享:ASP.NET CORE 项目实战(第四章)】Code First 创建数据库和数据表

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 本章我们来介绍下Asp.net Core 使用 CodeFirst 创建数据库和表,通过 控制台 和 dotnet ef 两种 ...

  6. C# if中连续几个条件判断

    C# if中连续几个条件判断 1.if (条件表达式1 && 条件表达式2) 当条件表达式1为true时 using System; using System.Collections. ...

  7. c++ vector 使用

    1. 包含一个头文件: 1 #include <vector> 2. 申明及初始化: std::vector<int> first; // empty vector of in ...

  8. windows7命令帮助大全

    有关某个命令的详细信息,请键入 HELP 命令名ASSOC 显示或修改文件扩展名关联.ATTRIB 显示或更改文件属性.BREAK 设置或清除扩展式 CTRL+C 检查.BCDEDIT 设置启动数据库 ...

  9. python之最强王者(5)——Nunber(数字)

    1.Python Number(数字) Python Number 数据类型用于存储数值. 数据类型是不允许改变的,这就意味着如果改变 Number 数据类型的值,将重新分配内存空间. 以下实例在变量 ...

  10. 用C#从数据库动态生成AdminLTE菜单的一种方法

    当前的应用设计风格趋于Flat扁平化,很多基于BootStrap实现了很多UI非常漂亮的管理界面(Bootstrap admin template). 此核心文件开源在Github:https://g ...