iT邦幫忙

2021 iThome 鐵人賽

DAY 28
4
Modern Web

JavaScript 魔法入門 - 從入門到中階觀念系列 第 28

中階魔法 - this 指向(二)

前情提要

上回提到魔法學姊艾草(鳥)在練習英文。

艾草:「This, These, That, Those ...,可惡鳥嘴太難發音了吧,啾啾啾~啾啾啾!」

「少來,平常講話講得很順,這時候倒是怪起鳥嘴了!你幹嘛最近那麼認真再唸英文呀?」

艾草:「嘿嘿嘿,當然是為了唸魔法咒語的時候看起來更酷呀!啊哈哈哈哈。」

「...好唷,我來教你吧,做為交換,昨天教學的 this 可以多說一些嗎?什麼指向魔法那邊聽不太懂。」

艾草:「好耶!那現在就開始吧 ~~」


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();

可以看到透過 setTimeoutconsole.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 指向全域的情況,接著來聊聊其他方法。


其他方法

this 指向其它變數

可以先於 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() 綁定

透過 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 文章程式碼都用艾草來舉例,到底誰會想一直跟自己打招呼啦,當初的自己到底在想啥呢...


總結

  • Callback Function 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


上一篇
中階魔法 - this 指向(一)
下一篇
魔法實作 - todolist
系列文
JavaScript 魔法入門 - 從入門到中階觀念30

2 則留言

0
juck30808
iT邦新手 2 級 ‧ 2021-10-12 18:38:20

恭喜大大即將完賽XD !!!

艾草 iT邦新手 4 級 ‧ 2021-10-13 13:46:14 檢舉

謝謝你,感恩的心 /images/emoticon/emoticon41.gif

0
龜人
iT邦新手 3 級 ‧ 2021-10-12 22:36:32

我的鬧鐘鈴聲~

我要留言

立即登入留言