JavaScript 深刻解剖bind内部机制

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

一、 先看一段代码:

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。bash

二、手动实现的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 中使用硬绑定函 数)app

三、解释new操做符

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

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

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

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

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。 详细解释一下:ui

  • 一、dd 是foo.bindNew(obj2)执行后,返回的一个函数
  • 二、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;
复制代码

注意 :this

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

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

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

此时的 fToBind ,是以前执行 bindNew 指向的fooprototype

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

(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
复制代码
相关文章
相关标签/搜索