Day 15:this 關鍵字 (1)
Last updated
Last updated
今天驟聞武俠大師金庸查先生逝世的消息,身為一位超過二十年的金迷,實在難以表達心中的難過。在此偷渡對一代文學大師的懷念,聊表追思,紀念這個對華文世界影響至深之偉人殞落的日子:金庸武俠,永垂不朽,緬懷再三,一路好走。—— 2018.10.30
this
是一個特殊的關鍵字,代表著一個物件,在很多程式語言都可以看到這個設計。由於不同程式語言有各自的特性, this
的運作方式也不盡相同。
那在 JavaScript 裡,this
指什麼呢?
來看看 W3Schools 的說明:
In a method, this refers to the owner object.
Alone, this refers to the global object.
In a function, this refers to the global object.
In a function, in strict mode, this is undefined.
In an event, this refers to the element that received the event.
Methods like call(), and apply() can refer this to any object.
JavaScript 的 this
一下是物件本身,一下是 Global 物件,一下是 undefined
,一下還可以是任何物件!
(Source: 網路圖片)
this
是程式語言中很重要的部分,誤判 this
所代表的物件會直接對程式的運作造成影響。所以本篇文章就來好好弄清楚 JavaScript 的 this
。
this
的簡單範例照例先上一個基本款的情境範例:
這是一個典型的 this
應用例子,對物件進行物件導向風格的操作。上面這個例子看起來很好懂,那 JavaScript 的 this
到底複雜在哪?
this
很複雜?像 Java
、C#
等基於類別的物件導向語言 (Class-based Object-oriented Languages),由於語法規範極為嚴謹,大多數情境可以從語彙範圍 (Lexical Scope) —— 也就是看語法上定義在哪個地方來判斷。
但 JavaScript 的 this
關鍵字運作與其他語言不同。具體來說,JavaScript 的 this
不是看定義的語彙位置,而是根據執行當下誰擁有這段程式碼。
W3Schools:
this
has different values depending on where it is used. The JavaScriptthis
keyword refers to the object it belongs to.
換句話說,不是看該屬性或函式被定義在哪個物件內,看的是執行當下被誰呼叫。
JavaScript 的語彙定義上沒有像類別那樣完整明確的物件界線,加上語法結構設計上很寬鬆,衍生出各種複雜情境。
此外,不同情境下,this
運作的機制也不同,例如:
全域執行環境 (Global Context) 下和函數執行環境 (Function Context) 下不同。
在嚴謹模式 (Strict Mode) 與一般模式下也可能有所不同。
(Source: 網路圖片)
理論描述太抽象,接下來會試圖用各種實際的例子來探討 this
的運作。
全域環境下比較單純。
MDN: In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.
this
在所有函式以外的全域執行環境下,會被當作全域物件,無論是否處於嚴謹模式。
HTML 中就是 window
物件。
Node.js 中就是 global
物件。
例如在全域執行環境下直接印出 this
物件:
一般模式:
嚴謹模式:
函數環境下可能遇到的情境就複雜了。
MDN: Inside a function, the value of this depends on how the function is called.
在函數內的 this
值取決於該函數如何被呼叫。
就先記著一個大原則:看是誰呼叫的,然後用這個原則來看各種情境下 this
運作的例子。
this
物件:呼叫者本身。
這應該是最常見、也相對好理解的情境,也就是 Object Method Binding。
在物件函式的用法,嚴謹模式或一般模式都是一樣的執行結果。
但同樣是物件函式的用法,也有很多種語法情境。
下面例子中 player
物件有 2 個函式,其中一個直接在函數內回傳 this
,藉此來觀察當下的 this
值:
執行的函數是 player.whatsThis
和 player.getName
所指向的函數。
呼叫者是 player
。
因此函數內的 this
指的是 player
。
這個例子理解上應該沒什麼困難,很單純的呼叫物件函式用法,加上函數定義就在物件內,判斷上很簡單。
如果函數定義不在物件內呢?
函數不是物件本身自己定義,而是指向別人的函數,就像跟別人借用函數一樣。
那在函數裡的 this
,會是物件本身,還是別人?
是不是開始有趣了。
來看下面的例子:
getName()
和 whatsThis()
函數都不是定義在 player
內。
但執行當下,player
是呼叫者,被視為那段程式碼的擁有者,因此 this
仍是 player
。
函數是否為物件本身的語彙構件無所謂,誰是最直接的呼叫才是最重要的。
物件內的屬性可以是另一個物件,另一個物件也可以有自己的函式,那這時候的 this
是誰?
例如以下例子:
player
物件擁有另一個物件 pet
,而 player
和 pet
都借用 getName()
。
player.f()
的呼叫者是 player
。
player.pet.f()
的呼叫者是 player.pet
。
這裡的原則沒有變,一樣看誰是呼叫者,誰就是 this
。
只是不要混淆,player.pet.f()
的呼叫者是 player.pet
而不是 player
。