首先,可以先看看文章:淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂,讀完內文先記得一件事: 「 脫離了物件,this 的值就沒什麼意義 」。
沒有意義是指 this
值常常會出乎你意料,在物件中很單純,除了特意改變 this
的值,不然 this
就是指向物件本身或 instance。
而接下來要討論的就是 this
為什麼這麼多變,又是怎麼改變的。
在非物件導向的環境下,this 會是預設值,而 this
的預設值會依據環境的不同而有所改變。
this
的預設值:
global
window
undefined
( node.js 跟瀏覽器都一樣 )想要把 this 統一的話,其實可以改成嚴格模式: 'use strict';
,這樣 this
就會變成 undefined
this 的值很常被搞混,是因為不清楚 this 的由來,先看一下範例:
'use strict';
const obj = {
sayThis: function() {
console.log(this);
}
}
obj.sayThis(); // => { sayThis: [Function: sayThis] } => 就是物件 obj 本身
sayThis()
由 obj
呼叫,所以 this
很單純、就是 obj
。
而如果用一個變數 say
去接收 sayThis
這個 function
本身呢?
'use strict';
const obj = {
sayThis: function() {
console.log(this);
}
}
const say = obj.sayThis;
say(); // => 輸出 undefined
say.call('who'); // => 輸出 who
咦? 函式內容不變,this 怎麼不一樣?
注意看 say
只是指向 obj.sayThis
這個 function
地址,但並不是由物件呼叫的,在這裡等於是在全域環境下呼叫一個 function
,所以 this
會是預設值,但因為前面有指定嚴格模式,所以才會輸出 undefined
。
所以到目前為止,只要記得一件事就好,this 是在被呼叫的當下決定,跟在哪裡宣告沒有關係。
call
& apply
: 呼叫 function有兩個 method 能夠以指定的 this
立刻呼叫 function,call
、apply
:
'use strict';
function test(a, b, c) {
console.log('this: ', this);
console.log(a, b, c);
}
test(1, 2, 3); // this: undefined
test.call('I am call', 1, 2, 3); // this: I am call
test.apply('I am apply', [1, 2, 3]); // this: I am apply
undefined
.call(<this>, <arugemt_1, arugemt_2...>)
改變 this 為字串 I am call
.apply(<this>, [<arugemt_1, arugemt_2...>])
改變 this 為字串 I am apply
,第二個參數為一個陣列,裡面放參數所以看得出來 call
& apply
的差別,其實就只有參數是不是 array 的形式而已。
// 以下兩種寫法相等
obj.sayThis();
obj.stayThis.call(obj);
// 以下兩種寫法也相等( 瀏覽器中、且非嚴格模式 )
say();
say.call(window);
bind
: 回傳一個指定 this 的 function那如果我們只想要固定 this
值、沒有要立刻執行怎麼辦呢? 這時就可以用 bind
,用 function bind
會回傳固定 this
的 function 本身。
這樣就不用擔心因為呼叫當下而改變 this
,且之後就算用 call
or apply
也改變不了 this
。
const obj = {
sayThis: function() {
console.log(this);
}
}
const say = obj.sayThis.bind('hello');
say(); // => 輸出 [String: 'hello']
say.call('who'); // => 還是輸出 [String: 'hello']
this
像 DOM 物件綁定某種事件時,this
會變成綁定的 DOM 元素本身:
document.querySeletor('.btn').addEventListener('click', function(){
console.log(this); // => btn 這個元素
});
this
之前不是說過 this
跟在哪裡定義無關、而是跟在哪裡呼叫有關嗎?
但有個例外喔,就是箭頭函式。
'use strict';
class Test {
run() {
console.log('run: ', this);
setTimeout(function() {
console.log('setTimout: ', this);
}, 1000);
}
}
const test = new Test();
test.run();
// run: Test {}
// setTimout: undefined ( 一秒後 )
以上例子看起來很正常,沒有問題,那如果換成 arrow function 呢? 會發現 setTimout 的 this
變成 Test
了。
'use strict';
class Test {
run() {
console.log('run: ', this);
setTimeout(() => {
console.log('setTimout: ', this);
}, 1000);
}
}
const test = new Test();
test.run();
// run: Test {}
// setTimout: Test {} ( 一秒後 )
this
在 arrow function 中,跟在哪裡定義有關,此時的 this
有點像變數的行為,會依照作用域去抓外部的 this
,依據上一層的 run: this
是什麼,setTimout: this
就會是什麼。
要驗證的話,可以把 run
function 的 this
固定成一個字串 hello
,所以此處的 setTimout: this
也會跟著變成 hello
。
class Test {
run() {
console.log('run: ', this);
setTimeout(() => {
console.log('setTimout: ', this);
}, 1000);
}
}
const test = new Test();
const newTest = test.run.bind('hello'); // => 把 this 固定成 hello
newTest();
// run: hello
// setTimout: hello ( 一秒後 )
有很多因素要考慮:
global
window
undefined
( node.js 跟瀏覽器都一樣 )this
就是 new
出來的 instance
this
為綁定的 DOM 物件this
跟如何呼叫 function 有關
this
為物件本身this
則為預設值 ( global or window or undefined
)this
:bind
: 將 this
值固定下來,回傳 functioncall
or apply
: 指定 this
,傳入參數並執行 functionfunction log() {
console.log(this);
}
var a = { name: 'a', log: log };
var b = { name: 'b', log: log };
log(); // => global or window
a.log(); // => a 本身
b.log.apply(a); // => a 本身
const newLog = b.log.bind(b);
newLog.apply(a); // => b 本身
( 以上內容大部分是 程式導師實驗計畫第三期 的學習筆記,如有錯誤歡迎糾正,非常感謝 🤓 )
Written on August 9th, 2019 by Yakim shu