上一篇对call和apply的模拟实现做了一个梳理,可参见:模拟实现call、apply,下面将具体研究一下bind啦啦啦

1. bind和call/apply的差别

bind方法会创建一个新函数,返回值是一个绑定了上下文的函数

call和apply是将函数直接执行

描述:

bind()函数会创建一个绑定函数(bound function,BF),它包装了原函数对象,调用该绑定函数即执行原函数

返回值:是一个原函数拷贝,并拥有指定的this值和初始参数

当一个绑定函数是用来作为构造函数即使用new操作符去构造一个新实例时,原来bind绑定的this将被忽略,this将指向当前新创建的实例。但是提供的参数列表仍然会插入到构造函数调用时的参数列表之前

 

2. bind的模拟实现

bind的几个特性:

  1. 指定this
  2. 返回值为一个函数
  3. 可以传参数
  4. 函数柯里化

 第一步:基本实现以上四个特性

    Function.prototype.bind2 = function (context) {
        var self = this;  // 这句实际上是把调用bind2的函数赋给self,console.log(self)得到的是一个函数
        var args = Array.prototype.slice.call(arguments, 1);  // 获取传入的参数,将其变为数组存入args中

        return function () {
            var funArgs = Array.prototype.slice.call(arguments);  // 这里的arguments是这个return函数中传入的参数
            return self.apply(context, args.concat(funArgs))  // 将this指向context,将self的参数和return的function的参数拼接起来
        }
    }

说明:

  1. var self = this;不是常理中我们想象的将this的值保存在self,这里是将整个函数赋给self,其实也等同于将this指向调用者
  2. 因为arguments是类数组对象,所以这里还是使用Array.prototype.slice.call的方式将arguments存储在args数组中
  3. funArgs得到的是返回的function中带的参数,实际上就是实现柯里化的一种参数获取方式!

第二步:实现bind特性

当绑定函数使用new操作符创建对象时,会使原this的指向失效,this会指向新的对象实例!

    var value = 2;
    var foo = {
        value: 1
    };
    function bar(name, age) {
        this.habit = 'shopping';
        console.log(this.value);
        console.log(name);
        console.log(age);
    }
bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'Jack'); var obj = new bindFoo(20); // undefined // Jack // 20

首先将foo这个对象绑到bar这个函数上,传入参数“Jack”,然后将绑定函数bindFoo()作为构造函数生成新的对象obj,此时bindFoo的this是指向obj的,这里可以看到this.value并没有输出1,而是输出了undefined。因为obj没有value值!

因此这里将通过修改返回函数的原型实现:

    Function.prototype.bind2 = function (context) {

        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);

        var fNOP = function () { };  // 1 创建一个空对象

        var fBound = function () {
            var funArgs = Array.prototype.slice.call(arguments);
            return self.apply(
                this instanceof fNOP ? this : context,  // this instanceof fNOP为true时,说明返回的fBound被当做构造函数调用;为false时,this指向context
                args.concat(funArgs)
            );
        }

        fNOP.prototype = this.prototype;  // 2 空对象的原型 指向 绑定函数的原型
        fBound.prototype = new fNOP();  // 3 然后将空对象实例赋给fBound.prototype

        return fBound;
    }

以上已基本实现bind的功能

  • 说明:注释中的123三步是利用空对象作为中介的方式来维护原型关系,实际上完成的功能是:修改返回函数的prototype为绑定函数的prototype —— 完成这三步后,new出来的实例就可以继承绑定函数的原型中的值!拿上题为例,即obj可以获取到bar.prototype的friend值。

第三步:

当调用bind调用的不是函数时需要抛出异常!因此加上如下代码就得到bind模拟的最终的完整版:

    Function.prototype.bind2 = function (context) {
        // 如果bind绑定的不是函数,则抛错
        if (typeof this !== "function") {
            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 funArgs = Array.prototype.slice.call(arguments);
            return self.apply(
                this instanceof fNOP ? this : context,
                args.concat(funArgs)
            );
        }

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
    }