iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 24
0

一切都按順序來

預設繫結 ( default binding ) 是這四個規則中優先序最低的。

Test 1 隱含繫結 和 明確繫結

function foo(){
    console.log( this.a );
}

var obj1 = {
    a: 2,
    foo: foo
};

var obj2 = {
    a: 3,
    foo: foo
}

obj1.foo(); // 2

obj1.foo.call( obj2 ); // 3

從結算的流程來看

  1. 結算 obj1.foo 就成為 foo(),此時的 this 指的是 obj1。
  2. foo.call( obj2 ),會再把 this 指定到 obj2 上。

明確的繫結會大於隱含的繫結。
(這個結論 Tony 不是很喜歡。因為照著邏輯結算,也是得到這樣的結果。之後再補上測試流程好了。)


Test 2 隱含繫結 + new 繫結

function foo( something ) {
    this.a = something;
}

var obj1 = {
    foo:foo
};

var obj2 = {}

obj1.foo( 2 );         
console.log( obj1.a );       // 2

obj1.foo.call(obj2, 3); 
console.log( obj2 );         // { a: 3 }

var bar = new obj1.foo( 4 );
console.log( obj1.a );       // 2
console.log( bar.a );        // 4

隱含的繫結是在執行函式,把值賦予當下的物件。

這時候的 new obj1.foo(4) 會把值灌給 obj1 嗎?
不會。
而是直接把 this 指向 bar。
這時候呼叫 bar,就會得到 4。

new 會大於隱含的繫結

Test 3 硬繫結 vs New

function foo( something ) {
    this.a = something;
}

var obj1 = {};

var bar = foo.bind( obj1 );
bar( 2 );

console.log( obj1.a );    // 2

var baz = new bar( 3 );
console.log( obj1.a );    // 2
console.log( baz.a );     // 3

這個例子 只要呼叫 bar 就會硬繫結到 obj1。

但是在 new bar( 3 ) this 還是會指向 obj1 嗎?
不會。

new 繫結 會大於 硬繫結。

決定 this 為何

  1. new 繫結 - new 關鍵字。
  2. 明確的繫結 - call apply or bind 的硬繫結。
  3. 隱含的繫結 - 函式內呼叫。
  4. 預設繫結 - 是否在嚴格模式。是 undefined,否 global。

繫結的例外

忽略 this

如果塞在 call、apply、bind 裡面的是 null、undefined。等同於忽略這些繫結。

function foo() {
    console.log( this.a );
}

var a = 2;

foo.call( null ); // 2

什麼情況會塞 null 在 call 裡面啊?

null 是佔位值。

function foo(a,b) {
    console.log( "a: " + a + ", b: " + b);
    // 輸出 "a: a, b: b"
}
foo.apply( null, [2, 3]); // a: 2, b: 3

var bar = foo.bind( null, 2 );
bar( 3 );                 // a: 2, b: 3

用 apply(..) 當作是將陣列拆開,變成參數輸入很常用。
用 bind(..) 。
但你就是需要忽略 this 的存在。請輸入 null。

BUT!
如果你使用的 API 裡面有函式掛著 this ,但是你依然用了 null 去佔位。結果真的找到了他全域的變數,就會很麻煩了。

更安全的 this

你可以設定一個等同於 null 的值 DMZ (demilitarized zone 非軍事區 ) 去佔位。

然後在自己的全域也設定一個,這時候 this 就會指向 DMZ。

他是個物件,帶著你喜愛的符號。作者推薦 mac 的 option + o,可以得到 ø。

function foo(a,b) {
    console.log( "a: " + a + ", b: " + b);
}
var ø = Object.create( null );

foo.apply( ø, [2, 3] ); // a: 2, b: 3

var bar = foo.bind( ø, 2 );
bar( 3 ); // a: 2, b: 3

這時候,你的 this 都會指向 DMZ。就算是預設繫結也沒問題。

間接參考

function foo() {
    console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo();           // 3
(p.foo = o.foo)(); // 2

p 間接參考 o。

因為 p 沒有 foo,會由 o.foo 傳入,建立一個 foo: foo。
但這時候的執行位置,還是全域。(把它看成是 IIFE 吧。)

軟化繫結

硬繫結太硬,只有 new 可以破。

有其他比較軟一點方法呢? 用隱含和明確就可以修改。

書上有給軟繫結的工具,有興趣請看書上 softBind 的程式碼。

實際的運用,Tony 需要點時間了解,所以...

語彙的 this

ES6 引進的新規格:箭頭函式 ( arrow-function )

他用的規則和前面四個沒關係,箭頭函式是用語彙範疇。
( 這不就打之前 this or that 的嘴巴嗎?)

function foo() {
    return (a) => {
        console.log( this.a );
    }
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };

var bar = foo.call( obj1 );
bar.call( obj2 );  // 2,不是 3 !

下面還有兩個範例。

但主要都請以兩個方法作結

  1. 只使用語彙範疇。
  2. 完全擁抱 this 風格的機制,必要時用 bind(..),並試著避免用 self = this 和 箭頭函式。

結束啦!

this 是個神奇的旅程。
但最後兩個部分,的確有點難懂,特別是軟化繫結。
也剛好 Tony 對 setTimeout 有莫名的障礙。

但大致上都還是可以藉由閱讀程式碼了解文章內容。
好範例真的很重要。

那就

明天見啦~

參考資料

  1. 你所不知道的JS
  2. 克服 JavaScript 奇怪的地方
  3. MDN - 運算子優先序

上一篇
Day23 - This 現在全都說得通了!
下一篇
Day25 - 物件
系列文
你為什麼不問問神奇 JavaScript 呢?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言