接上篇文章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
後,改變了bar
的this
指向;使其新new
的例項 baz
;
foo
的this.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
複製程式碼