上回提到魔法學姊艾草(鳥)在練習英文。
艾草:「This, These, That, Those ...,可惡鳥嘴太難發音了吧,啾啾啾~啾啾啾!」
「少來,平常講話講得很順,這時候倒是怪起鳥嘴了!你幹嘛最近那麼認真再唸英文呀?」
艾草:「嘿嘿嘿,當然是為了唸魔法咒語的時候看起來更酷呀!啊哈哈哈哈。」
「...好唷,我來教你吧,做為交換,昨天教學的 this 可以多說一些嗎?什麼指向魔法那邊聽不太懂。」
艾草:「好耶!那現在就開始吧 ~~」
上一篇文章提到了一些常見 this
指向,這篇會提到取不到值的情境與解法。
透過上篇文章,可能會認為只要透過物件方法呼叫的函式,都會指向物件本身。但並不是只要透過此種方式呼叫就都會指向該物件,以 Callback Function setTimeout
來舉例:
var name = '全域艾草';
let obj = {
name: '艾草',
sayHello: function () {
console.log(`Hello , ${this.name}`);//Hello , 艾草
setTimeout(function () {
console.log(`Hello , ${this.name}`);//Hello , 全域艾草
}, 0);
}
};
obj.sayHello();
可以看到透過 setTimeout
去 console.log
印出的值是全域艾草,代表 this
是指向到全域,多數Callback Function 運作方式偏向簡易呼叫,因此 this
會指向全域。
如何讓 setTimeout
指向到物件內呢?解決的其中一個方式就是 - 箭頭函式!
箭頭函式沒有自己的 this
,所以會與外層函式作用域的 this
指向相同,將上方案例修改為箭頭函式:
var name = '全域艾草';
let obj = {
name: '艾草',
sayHello: function () {
console.log(`Hello , ${this.name}`);//Hello , 艾草
setTimeout(()=> {
console.log(`Hello , ${this.name}`);//Hello , 艾草
}, 0);
}
};
obj.sayHello();
透過箭頭函式沒有自己 this
的特性,this
會指向 obj
,也可以成功取用到 obj
物件內的屬性值。
但如果不夠掌握箭頭函式的特性,也可能會遇到莫名的地方。
var name = '全域艾草';
let obj = {
name: '艾草',
sayHello: () => {
console.log(`Hello , ${this.name}`);//Hello , 全域艾草
}
};
obj.sayHello();
像這樣當看到呼叫方式 obj.sayHello()
時可能會預期 this
是指向前方的 obj
,但印出結果 this
的指向卻是指向全域。
因為箭頭函式沒有自己的 this
所以它會與外層作用域的 this
指向相同,但箭頭函式 sayHello
外層並沒有其他函式作用域可以參考,所以它會指向到全域。
當然也不只箭頭函式可以解決 Callback Function 指向全域的情況,接著來聊聊其他方法。
可以先於 sayHello()
設定一個自定義名稱變數,這邊以常見的命名方式 self
舉例,並將該 self
指向 sayHello()
的 this
。
將 setTimeout
內要取用 this
的地方,替換成使用存取外層作用域 this
的變數 self
。
var name = '全域艾草';
let obj = {
name: '艾草',
sayHello: function () {
const self = this;
setTimeout(function () {
console.log(`Hello , ${self.name}`);//Hello , 艾草
}, 0);
}
};
obj.sayHello();
透過 bind()
並傳入參數為 this
想指向的地方,範例傳入obj
後,bind()
就會幫你綁定到 obj
上,因此可以順利取用到 obj
內的屬性值。
var name = '全域艾草';
let obj = {
name: '艾草',
sayHello: function () {
setTimeout(function () {
console.log(`Hello , ${this.name}`);//Hello , 艾草
}.bind(obj), 0);
}
};
obj.sayHello();
使用 bind() 綁定的其他例子
像這樣 bind
直接幫你把 this
綁定好並回傳綁好的函式給你,接下來不管你想怎麼呼叫它,它都不會變心,真的很棒!
var name = '全域艾草';
let obj = {
name: '艾草',
};
function sayHello(){
console.log(`Hello , ${this.name}`);//Hello , 全域艾草
}
sayHello();
let sayHello2 = sayHello.bind(obj);
sayHello2();//Hello , 艾草
最後的自我小吐槽:寫到這邊才發現這兩篇 this
文章程式碼都用艾草來舉例,到底誰會想一直跟自己打招呼啦,當初的自己到底在想啥呢...
setTimeout
會指向全域this
this
指向問題的方法
this
指向this
指向後,提供內層使用該變數bind()
綁定JavaScript 全攻略:克服 JS 的奇怪部分(Udemy)
Vue 3 實戰影音課程(六角學院)
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/this
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
https://realdennis.medium.com/javascript-聊聊call-apply-bind的差異與相似之處-2f82a4b4dd66