• bind方法会创建一个新函数,当这个函数被调用时,它的this值是传递给bind()的第一个参数,传入bind方法的第二个参数以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。bind返回的绑定函数也能使用new操作符创建对象;这种行为就像把原函数当成构造器,提供的this值被忽略,同时调用时的参数被提供给模拟函数。
  • bind和apply/call的区别
    bind方法会返回一个绑定上下文的函数
    apply/call是直接执行了函数
  • bind的特性
    1. 可以指定this
    2. 返回一个函数
    3. 可以传入参数
    4. 柯里化
  • bind()函数在ES5才被加入,IE8及以下浏览器并不支持
  • bind的实现

    1
    2
    3
    4
    5
    6
    7
    // 第一版
    Function.prototype.bind2 = function(context){
    var self = this; // 此this时绑定bind的函数,也就是调用者
    return function(){ // 实现第二步
    return self.apply(context) // 指定调用者的this为传入的context; 实现第一步
    }
    }

    测试以下我们上面写的第一步bind

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var value = 3;
    var foo = {
    value:2
    }
    function bar(){
    console.log(this.value)
    }
    var bindFoo = bar.bind2(foo)
    bindFoo(); // 2

    上面我们实现了一个非常简易的bind实现。下面我们继续第二步,给函数传入参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 第二版
    Function.prototype.bind2 = function(context){
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1) // 过滤掉指定的this
    return function(){
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(context, args.contact(bindArgs))
    }
    }

    在上面我们实现的第二版中,我们使用arguments获取到调用bind方法传入的参数(过滤掉绑定this的第一个参数);以及获取到返回函数的参数,然后合并两个参数给调用bind的原函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var value = 3;
    var foo = {value: 2}
    function bar(name, age){
    return {
    value: this.value,
    name: name,
    age: age
    }
    }
    var bindFoo = bar.bind2(foo, 'mark')
    bindFoo(20); // {value: 2, name: mark, age: 20}

    上面基本实现了bind。但是bind还有一个独特的特性就是可以使用new操作符来调用经过被bind包装之后返回的新函数,此时,通过调用bind绑定的this将会失效。这是由于new的this绑定顺序大于显示绑定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 第三版
    Function.prototype.bind2 = function(context){
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fBound = function(){
    var bindArgs = Array.prototype.slice.call(arguments);
    // 利用instanceof来判断是否是通过new操作符来调用的,如果是的话,则不改变this的指向(指向的是new出来的实例对象;如果不是的话,那么则将this指向传入进来的上下文context)
    return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs))
    }
    // 将返回函数的原型绑定到绑定函数的原型上,目的是为了让返回的函数能够调用绑定函数的原型链上的属性和方法
    fBound.prototype = this.prototype
    return fBound;
    }

    在上面我们实现的第三个版本中,我们将返回函数的原型直接关联到绑定函数的原型上面,但是这样关联如果修改了返回函数的原型,那么绑定函数上的原型也会被修改,所以我们可以通过原型继承来解决这个办法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 第四版
    Function.prototype.bind2 = function(context){
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function(){};
    var fBound = function(){
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fBound ? this : context, args.contact(bindArgs));
    }
    fNOP.prototype = this.prototype; // 将绑定函数的原型赋值给函数fNOP的原型对象。
    fBound.prototype = new fNOP(); // 将返回的函数的原型关联到fNOP实例出来的对象,这样修改返回函数的原型就不会直接修改到绑定函数的原型,而且还可以继承绑定函数的原型。
    return fBound;
    }

    上面的第四版中基本已经实现了bind函数的功能,但是还有一个小的细节需要注意一下就是,如果调用bind的不是一个函数,那么我们应该抛出异常报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 最终版
    Function.prototype.bind2 = function(context){
    if (typeof != 'function') { // 判断调用bind函数的是不是一个函数,如果不是一个函数,那么直接抛出错误
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function(){};
    var fBound = function(){
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fBound ? this: context, args.contact(bindArgs));
    }
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
    }