在前兩篇中我們詳細的介紹了 this 的五種繫結:
那當出現了同時符合兩種繫結的套用規則時,到底誰的優先權比較高呢?
在這篇中,我們會區分出 this 繫結規則優先權的高低,並讓之後看到 this 時,能夠正確的知道它的內容是甚麼。
由於預設繫結是在最陽春的狀況下才會套用,因此就不多著墨比較了。
首先就讓我們先把 隱含繫結 跟 其他繫結 做比較吧!
這個範例程式中,我們首先把 logMyString
這個 Function 設給 obj
的屬性 log
,因此執行 obj.log()
時就會是套用 隱含繫結,其 this 會是 obj
。
那假如我們使用 apply
來執行 obj.log
這個方法的話,究竟是會套用 隱含繫結 還是 明確繫結 呢?
var obj = { myString: 'hello obj', log: logMyString },
obj1 = { myString: 'hello obj1' };
function logMyString() {
console.log(this.myString);
}
obj.log(); // "hello obj"
obj.log.apply(obj1); // "hello obj1"
執行 obj.log.apply(obj1);
後我們可以看到 明確繫結 取勝了,此時吃到的 this
會是 obj1
,它把 隱含繫結 的 this
:obj
給覆蓋掉了。
很明顯的 明確繫結 > 隱含繫結。
接著來比較 隱含繫結 與 new 繫結。
這裡我們同樣使用 obj.log
來試圖把 logMyString
的 this
設為 隱含繫結,然而這邊我們使用 new
把 obj.log
當作 建構式 執行:
var obj = { myString: 'hello obj', log: logMyString };
function logMyString() {
this.myString = 'hello constructor';
}
var newObj = new obj.log();
console.log(obj.myString); // "hello obj"
console.log(newObj.myString); // "hello constructor"
可以發現,new 繫結 蓋過了 隱含繫結,因為我們 obj.myString
的值依然保持著 "hello obj"
,而被 new
新建出來的物件則多了一個 myString
屬性,裡面的內容確實為我們給的 "hello contructor"
。
因此我們得到了 new 繫結 > 隱含繫結 的結論。
現在兩個贏家 明確繫結 與 new 繫結要來做個大比拚了。
要先解釋的是,因為我們無法把 call
、apply
拿去跟 new
去做比較,因為他們同時都是執行 Function 的方法。因此我們從明確繫結中派出了 bind
來跟 new
做比較。
按照之前我們所製作的 bind polyfill 推測,new
應該是無法覆蓋 bind
指定的 this
的,因為 polyfill 中我們是將傳入 bind
的第一個參數,利用 apply
再指定給目標函式的。就讓我們實際測看看。
var obj = { myString: 'hello obj' };
function logMyString() {
this.myString = 'hello constructor';
}
var bindedFn = logMyString.bind(obj),
newObj = new bindedFn();
console.log(obj.myString); // "hello obj"
console.log(newObj.myString); // "hello constructor"
我們將 logMyString.bind(obj)
回傳的強制繫結包裹函式指派給了 bindFn
,並再用 new
去執行它。很驚訝地發現,new 繫結 的優先權 竟然大於 bind
。
我們可以發現 MDN 上的 bind polyfill 較為複雜,而它可以在判斷是 new 繫結 的時候就選擇使用 new 繫結的 this,而非之前強制綁定的 this。
再次的由 new 繫結 取勝。new 繫結 > 明確繫結。
這次就由 語彙繫結 來跟 明確繫結 做比較。
var obj = { myString: 'hello obj' },
obj1 = {myString: 'hello obj1'};
function outer() {
return () => {
console.log(this.myString);
}
}
var arrowFn = outer.apply(obj);
arrowFn(); // "hello obj"
arrowFn.call(obj1); // "hello obj"
new arrowFn(); // Uncaught TypeError: arrowFn is not a constructor
為了能更清楚辨別 語彙繫結的 this,我們先把 outer
中的 this 利用 apply
指定為 obj
(這只是前置步驟而已)。接下來確定一下 arrowFn
的 this 真的有綁訂到 obj
。沒錯,執行完的結果確實是obj.myString
的值 "hello obj"
。
重頭戲來了,接著我們試圖利用 call(obj1)
來試圖將 arrowFn
內的 this 綁定為 obj1
。結果發現語彙繫結還是更勝一籌,arrowFn
的 this.myString
仍然印出了 obj.myString
的值,也就是 明確繫結 無法覆蓋由 語彙繫結指定的 this。
這符合我們之前利用 Closure 來自製 語彙繫結 的行為,因為在自製的語彙繫結中,因為我們只操作變數內容為外層變數的 this,因此我們並不在意 arrowFn
的 this
為何。
因此 語彙繫結 > 明確繫結。
在最後一行 new arrowFn()
中我們可以發現 箭號函式是無法作為 建構式使用的,也就是說 箭號函式是無法跟 new 繫結來做比較的。然而還是回到我們自製的 語彙繫結 其實並不在意繫結到內層 Function 的 this 到底為何,因此在這裡我還是會認為 語彙繫結 > new 繫結。
這篇中,我們詳細比較了 this 五種繫結之間套用的優先順序,可以歸納出套用的順序如下
會把 語彙繫結 擺在 new 繫結 之前是因為我們自製的 語彙繫結 其實並不在意繫結到內層 Function 的 this 到底為何,而是直接使用外層 Function 的 this,因此才做了這樣的順序安排。
You Don't Know JS: this & object prototypes
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Function/bind