阅读 174

JavaScript 深入解剖bind内部机制

接上篇文章JavaScript重识bind、call、apply

1、 先看一段代码:

function foo(something) {
    this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 ); // obj1.a === 2
var baz = new bar(3);
console.log( obj1.a ); // 2 
console.log( baz.a ); // 3
复制代码

bar 被硬绑定到 obj1 上,但是 new bar(3) 没有将obj1.a 修改为 3。相反,new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this。因为使用了 new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。

2、手动实现的bind代码

if (!Function.prototype.bindNew) {
    Function.prototype.bindNew = function(oThis) {
        //一个函数去调用,也就是说bind,call,apply的this是个函数;
        //然后再去改变这个函数里面的this;
        if (typeof this !== "function") {
         // 与 ECMAScript 5 最接近的
         // 内部 IsCallable 函数 
         throw new TypeError(
          "Function.prototype.bind - what is trying " +
         "to be bound is not callable"
         ); 
        }
        //这里将初始化的参数缓存起来;
        var aArgs = Array.prototype.slice.call( arguments, 1 ),
        // ftoBind 指向要bind的函数;
        fToBind = this,
        // 返回一个新函数
        fNOP = function(){}, 
        fBound = function(){
        //fToBind.apply 改变绑定this;
        // 执行的时候判断,当前this等于fNOP并且传入oThis,就设置成当前this,不然就改变成初始化传入的oThis;
           return fToBind.apply( 
            (this instanceof fNOP && oThis ? this : oThis ),
            aArgs.concat(Array.prototype.slice.call( arguments ) )
            ); 
        };
        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
    };
}

复制代码

(后面会介绍为什么要在 new 中使用硬绑定函 数)

3、解释new操作符

1️⃣使用bindNew来模拟bind内部机制

下面是 new 修改 this 的相关代码:

this instanceof fNOP &&
oThis ? this : oThis ; 
// ... 以及:
fNOP.prototype = this.prototype; 
fBound.prototype = new fNOP();
复制代码

这段代码会判断硬绑定函数是否是被 new 调用,如果是的话就会使用新创建 的 this 替换硬绑定的 this。 如果你这样子调用:

function foo() {
    console.log("name: " + this.name);
}
var obj = { name: "obj" };
var obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
var dd = foo.bindNew(obj2);
var dj = new dd();// name:undefined;   而不是name:obj2
复制代码

因为new操作修改了this的指向;this绑定的就是是新创建的对象-dj。 详细解释一下:

  • 1、dd 是foo.bindNew(obj2)执行后,返回的一个函数
  • 2、dd这个函数是:
// ftoBind 指向要bind的函数; 这里是foo;
fToBind = this,
// 返回一个新函数
fNOP = function(){}, 
fBound = function(){
    //fToBind.apply 改变绑定this;
    // 执行的时候判断,当前this等于fNOP并且传入oThis,就设置成当前this,不然就改变成初始化传入的oThis;
    return fToBind.apply( 
    (this instanceof fNOP && oThis ? this : oThis ),
        aArgs.concat(Array.prototype.slice.call( arguments ) )
    ); 
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
复制代码

注意 :

// fNOP的原型指向this的原型,this此时指向foo;
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
复制代码

这个代码使 fBound 为 fNOP 的实例;

  • 3、new dd()后,就执行fBound这个函数 里面的代码:
return fToBind.apply( 
    (this instanceof fNOP && oThis ? this : oThis ),
        aArgs.concat(Array.prototype.slice.call( arguments ) )
复制代码

此时的 fToBind ,是之前执行 bindNew 指向的foo

此时 this,就是指向的new dd() 后返回的新实例; this instanceof fNOP === true

(this instanceof fNOP && oThis ? this : oThis ) 这个就返回 this; 那么这个新对象上面是没有obj这个属性的,foo.apply,执行foo后,就打印出name:undefined;

2️⃣使用bind

上面是手写bind然后来剖析bind内部的绑定机制;那么我们实际检测也会等到同样的结果; 就是本文最开始的代码:

function foo(something) {
    this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 ); // obj1.a === 2
var baz = new bar(3);
console.log( obj1.a ); // 2 
console.log( baz.a ); // 3
复制代码

这样就明白了为什么baz.a 是3,而不是2了,因为new后,改变了barthis指向;使其新new的实例 baz; foothis.a = something; 就将 baz.a = 3了; 这里也可以得出结论,new 操作改变this绑定的优先级高于硬绑定(bind,apply,call);

3️⃣new和bind的特性的应用

如果 new 中使用硬绑定函数,就可以预先设置函数的一些参数,这样在使用 new 进行初始化时就可以只传入其余的参数。bind(..) 的功能之一就是可以把除了第一个 参数(第一个参数用于绑定 this)之外的其他参数都传给下层的函数(这种技术称为“部 分应用”,是“柯里化”的一种)。举例来说:

function foo(p1,p2) { 
    this.val = p1 + p2;
}
// 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么 
// 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" ); 
baz.val; // p1p2
复制代码
关注下面的标签,发现更多相似文章
评论