iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 19
0
Modern Web

I Want To Know JS系列 第 19

JavaScript - This Priority

  • 分享至 

  • twitterImage
  •  

在前兩篇中我們詳細的介紹了 this 的五種繫結:

  • 預設繫結
  • 隱含繫結
  • 明確繫結
  • new 繫結
  • 語彙繫結

那當出現了同時符合兩種繫結的套用規則時,到底誰的優先權比較高呢?

在這篇中,我們會區分出 this 繫結規則優先權的高低,並讓之後看到 this 時,能夠正確的知道它的內容是甚麼。

由於預設繫結是在最陽春的狀況下才會套用,因此就不多著墨比較了。

首先就讓我們先把 隱含繫結 跟 其他繫結 做比較吧!

隱含繫結 vs 明確繫結

這個範例程式中,我們首先把 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,它把 隱含繫結 的 thisobj 給覆蓋掉了。

很明顯的 明確繫結 > 隱含繫結

隱含繫結 vs new 繫結

接著來比較 隱含繫結 與 new 繫結。

這裡我們同樣使用 obj.log 來試圖把 logMyStringthis 設為 隱含繫結,然而這邊我們使用 newobj.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 繫結 > 隱含繫結 的結論。

明確繫結 vs new 繫結

現在兩個贏家 明確繫結 與 new 繫結要來做個大比拚了。

要先解釋的是,因為我們無法把 callapply 拿去跟 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 繫結 > 明確繫結

語彙繫結 vs 明確繫結

這次就由 語彙繫結 來跟 明確繫結 做比較。

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。結果發現語彙繫結還是更勝一籌,arrowFnthis.myString 仍然印出了 obj.myString 的值,也就是 明確繫結 無法覆蓋由 語彙繫結指定的 this。

這符合我們之前利用 Closure 來自製 語彙繫結 的行為,因為在自製的語彙繫結中,因為我們只操作變數內容為外層變數的 this,因此我們並不在意 arrowFnthis 為何。

因此 語彙繫結 > 明確繫結

在最後一行 new arrowFn() 中我們可以發現 箭號函式是無法作為 建構式使用的,也就是說 箭號函式是無法跟 new 繫結來做比較的。然而還是回到我們自製的 語彙繫結 其實並不在意繫結到內層 Function 的 this 到底為何,因此在這裡我還是會認為 語彙繫結 > new 繫結。

小結

這篇中,我們詳細比較了 this 五種繫結之間套用的優先順序,可以歸納出套用的順序如下

  1. 語彙繫結
  2. new 繫結
  3. 明確繫結
  4. 隱含繫結
  5. 預設繫結

會把 語彙繫結 擺在 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


上一篇
JavaScript - This (2)
下一篇
JavaScript - 物件導向 (1)
系列文
I Want To Know JS32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言