準備知識
使用new來呼叫函式會自動執行下面的操作:
- 建立一個全新的物件
- 這個新物件會被執行原型連線
- 這個新物件會繫結到函式呼叫的this
- 如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件
注意this繫結規則,new操作具有最高的優先順序
《你不知道的JavaScript(上卷)》提供了一個例子,bar被硬繫結到obj上,但是new bar(3) 並沒有像我們預計的那樣把obj.a修改為3。相反,new修改了硬繫結呼叫bar()中的this。因為使用了new繫結,我們得到了一個名字為baz的新物件,並且baz.a的值為3。
function foo(something) {
this.a = something
}
var obj = {}
var bar = foo.bind(obj)
bar(2)
console.log(obj.a) //2
var baz = new bar(3)
console.log(obj.a) //2
console.log(baz.a) //3
複製程式碼
instanceof運算子的第一個變數是一個物件,暫時稱為A;第二個變數一般是一個函式,暫時稱為B。
instanceof判斷準則:沿著A的__proto__這條線來找,同時沿著B的prototype這條線來找,如果兩條線能找到同一個引用,即同一個物件,那麼就返回true。
原始碼分析
MDN上提供的polyfill如下,主要的疑惑點應該就是 this instanceof fNOP 作用是什麼?
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== `function`) {
throw new TypeError(`Function.prototype.bind - what is trying to be bound is not callable`)
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP // 這段程式碼會判斷硬繫結函式是否是被new呼叫,如果是的話就會使用新建立的this替換硬繫結的this
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)))
}
// 維護原型關係
if (this.prototype) {
// Function.prototype doesn`t have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP()
return fBound
}
}
複製程式碼
this instanceof fNOP 單獨看是看不明白的,需要結合以下程式碼才能說明它的作用
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP()
複製程式碼
首先我們要清楚:bind(…)會返回一個硬編碼的新函式,它會把引數設定為this的上下文並呼叫原始函式。
重點就是bind(…)返回的是一個函式!函式!函式!這意味著可以對bind(…)返回的函式進行new操作,那麼問題就來了。
this繫結中new操作具有最高的優先順序,如果執行new操作,bind(…)應該不起作用,this應該指向new出來的新物件。
清楚了以上內容,我們開始閱讀程式碼。
if (typeof this !== `function`) {
throw new TypeError(`Function.prototype.bind - what is trying to be bound is not callable`)
}
複製程式碼
bind(…)必須由函式呼叫,所以以上程式碼對this的型別進行檢查,如果不是函式型別則丟擲錯誤。
var aArgs = Array.prototype.slice.call(arguments, 1)
var fToBind = this
var fBound = function() {
return fToBind.apply(this instanceof fNOP // 這段程式碼會判斷硬繫結函式是否是被new呼叫,如果是的話就會使用新建立的this替換硬繫結的this
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)))
}
複製程式碼
aArgs獲取傳入的其它引數,fToBind獲取需要硬繫結的函式,fBound為返回的繫結操作函式,我們先忽略fBound裡面的內容,繼續往下看。
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP()
複製程式碼
我們假設對bind(…)返回的函式進行new操作(原型鏈如下),則this instanceof fNOP 為true,此時我們就知道執行了new操作,硬繫結的this不能生效,需要把this繫結到新生成的物件上。
如果沒有進行new操作的話,就用apply模擬bind繫結,一切按照原計劃進行。
最後我們分析一下維護原型關係的重要性,例子如下:
function Foo(){
console.log(this.a);
this.a=1;
}
Foo.prototype.show=function() {console.log(this.a)};
Foo(); // undefined
var obj1=new Foo();
obj1.show();
var bar=Foo.bind({a:2});
bar(); // 2
var obj2=new bar();
obj2.show();
複製程式碼
因為bind函式內部保持了原型關係的繼承,所以物件obj2才能訪問到原型上的show方法。
** 注意:Foo.show()是錯誤的,因為Foo的原型指向的是Function.prototype,只有Foo的例項才能呼叫show方法。