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
複製代碼
相關文章
相關標籤/搜索