JavaScript

2022.05.24

JavaScript 的 this 到底指向誰?

書籍參考

最近趁閒暇之餘會閱讀 JS 專業書籍,目前在看的是由侯策所寫的前端工程進階大師指南,有興趣的朋友可以點擊以下書籍資訊,連至博客來網站。

頂級網站技術長高度:前端工程進階大師指南

頂級網站技術長高度:前端工程進階大師指南

書名:頂級網站技術長高度:前端工程進階大師指南,語言:繁體中文,ISBN:9789865501877,頁數:656,出版社:深智數位,作者:侯策,出版日期:2021/04/19,類別:電腦資訊

https://www.books.com.tw/products/0010888346

這邊會針對看完的章節,做一些簡單的分享與個人的理解筆記,有興趣的朋友可以參考看看,如有任何錯誤也請指正囉!

this 的指向

有一種廣為流傳的說法是:誰呼叫 thisthis 就指向誰。

而書中有作者統整的幾個 this 指向規律:

  • 於函式本體呼叫 function 時,在嚴格模式下,函式內的 this 會指向 undefined ;非嚴格模式下,this 會指向全域物件 (ex. window / global) 。
  • new 方法呼叫建構函式時,函式內的 this 會指向新建立的物件。
  • 透過 call / apply / bind 等方法呼叫函式時,函式內的 this 會指向指定參數的物件。
  • 透過上下文物件呼叫函式時,函式內的 this 會指向該物件。
  • 在箭頭函式中,this 的指向,是由外層 (ex. 函式或全域) 作用域來決定的。

以下針對該五種狀況,做更深入的筆記與範例介紹。

於函式本體呼叫 function

標題這句話轉譯成程式碼,大致如下,就是很單純的呼叫 function

function fn1() {
  console.log(this)
}

function fn2() {
  'use strict'
  console.log(this)
}

fn1() // window
fn2() // undefined

兩種的差別,在於有無使用嚴格模式 ('use strict')。

什麼是嚴格模式

我個人覺得嚴格模式有點像 ES Lint 或 TypeScript,就是讓你用更嚴謹的寫法去寫 JavaScript。如果要使用嚴格模式,只要將 'use strict' 加上程式碼前方即可,也可以只加在 function 裡,例如上述範例的第 6 行,這樣就只對 fn2() 有作用。

不能用在嚴格模式的寫法

關於在嚴格模式中,哪些寫法不能寫?我覺得 itsems 的 Javascript 的嚴格模式 (Strict Mode):不讓你錯 已經寫得很清楚了,以下擷取標題讓大家參考,文章內也有更詳細的範例程式碼,有興趣的朋友可以點擊閱讀。

直接定義未宣告變數

使用 delete 刪除變數或函式

重複變數

使用 8 進位值

使用 with

eval、arguments 不能當作變數名稱

this 禁止指向全域

Javascript 的嚴格模式 (Strict Mode):不讓你錯

而最後一點:this 禁止指向全域,就是我們要講的主題 XD,因此在嚴格模式下,this 會改指向 undefined

用 new 方法呼叫建構函式

當我們用 new 呼叫建構函式時,需要用 this 來創造新物件。這時候的 this,就會被綁在新物件上:

function Human(age) {
  this.age = age
}

const muki = new Human(20)
console.log(muki.age) // 20
Line 2: this 創造新物件

new 呼叫建構函式,實際上做了什麼事呢?以下提供簡略流程給大家參考:

  1. 建立一個新的物件
  2. 將建構函式的 this 指向剛建立的新物件
  3. 為這個物件增加屬性、方法 … 等
  4. 回傳新的物件

透過 call / apply / bind 等方法呼叫函式

利用 call(), apply() 以及 bind() 可以改變 this 的指向,以下舉個很簡單的例子 (大家應該都看到爛掉了 XD)

const obj = {
  name: 'muki'
}

function data() {
  console.log(this.name)
}

data.bind(obj)()

透過 bind()data 設定的 this 就會變成是 bind() 裡的物件。而 call() 以及 apply() 也是類似的概念,這邊就不再贅述了。

透過上下文物件呼叫函式

以上下文物件來判斷的話,基本上就是簡單的「誰呼叫 thisthis 就指向誰」的概念。

const person = {
  name: 'Lucas',
  brother: {
    name: 'Mike',
    fn: function() {
      return this.name
    }
  }
}
console.log(person.brother.fn())

由上述例子來看,最後呼叫 this 的是 person.brother 物件,因此 this.name 會是 Mike。

箭頭函式中的 this

在箭頭函式裡的 thisfunction 建立時,就已經由外層的作用域來決定 this 指向誰,而且不管用前面提到的 'use strict'call / apply / bind… 等,都沒辦法改變 this 的指向。

const button = document.querySelector('button')
const arrowFn = () => {
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行 function 時 this 指 Window
}
const fn = function () {
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行 function 時 this 指 HTMLButtonElement
}

button.addEventListener('click', arrowFn())
button.addEventListener('click', fn())

範例來源:https://pjchender.dev/javascript/js-arrow-function/

使用傳統 function 寫法,在 button 執行 click 事件後,this 就會指向呼叫他的 button 上。而使用箭頭函式,不管是誰呼叫,this 永遠是由外層的作用域來決定,不會被改變。

this 的優先順序

箭頭函式最高

包在箭頭函式裡的 this,無法被各種寫法改變指向,所以他的優先順序最高。

顯示綁定 (call / apply) 比隱式綁定高

接著來比較「以上下文物件呼叫」和「用 call / apply」指定 this,看哪一個為優先:

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

const obj1 = {
  a: 1,
  foo: foo
}

const obj2 = {
  a: 2,
  foo: foo
}

obj1.foo.call(obj2)   // output: 2
ojb2.foo.call(obj1)   // output: 1

如果沒有用 call 去指定 this,那 obj1.foo() 根據上下文判定,會顯示 1。但我們用了 call()this 指向 obj2,所以會顯示 2。

因此可以知道,call / apply 的優先順序會比較高。


原本只是想了解「嚴格模式」,卻一路回去看 this 的指向,然後將書上的介紹整理成讀書心得 xD。讓我想起多年前閱讀 深入淺出 JavaScript 時,也做過類似的讀書心得 XDDD。

希望可以藉由撰寫部落格,把 前端工程進階大師指南 閱讀理解完全,目前看來是本好書,我要慢慢的消化吸收,加油加油 💪!

歡迎給我點鼓勵,讓我知道你來過 :)

1
1
1
guest
0 則留言
Inline Feedbacks
View all comments
Copyright (C) MUKI space* / Reborn Theme All Rights Reserved.