記得初學 JavaScript 的時候,曾經和 42 的同學討論過學程式遇到的一些「抽象」的名詞概念,因為抽象所以難懂,我總是想要以類比的方式來找出一個合理且容易記住的解釋。這位同學本科是唸哲學(發現哲學轉職工程師的還真不少),他的第一個實習就找到了法國頗有名的電商公司。我們討論著 this 但是似乎怎麼解釋也無法清楚的描繪出來,只聽他重複著說著 this 就是 this…。
有些事真的需要「時間」來幫助我們理解,就像有些新認識的朋友,真的沒有相處過不會知道對方的個性,this 也是,寫 code 一陣子或許就會慢慢的、更清晰的去描繪 this 是怎麼回事。
「this」就像英文原意,是個代名詞,但更貼切的說 this 比較像是地方(空間)代名詞,以程式的面向來說也可以說是「物件」的代名詞,或是「執行環境」、「創建環境」或是「呼叫環境」?到底是哪一個,就要看用的是傳統或箭頭函式的寫法,或是…「呼叫」的方式?
我們常會說 this 到底是「指向」誰?那個誰應該就是包圍 this 的那個環境吧?我試著想像,這好像是我們說身體上的「指頭」,如果說手的指頭,那就是手臂;如果說是腳的指頭,就表示腿,這樣說當然只是一個比喻,但小鳥腦的我只能先這樣記著。
實際要理解 this 好像還是用程式會比較清楚,所以我們就以程式的方式來理解,也好險有時程式不用猜,把 this 用console.log()
印出來,是最清楚的。當然,試著瞭解其背後運作原理與規則,也會幫助我們少踩一些雷吧!
自從 ES6 的箭頭函式出現, this 的指向就又有了其他可能,原因是使用箭頭函式時, this 的指向是異於傳統函式寫法的指向,這部分我們留到下次再來討論。
傳統函式的 this 指向
先瞭解一下,一個函式本身,可包含的預設參數有哪些?
1 | var a = '全域宇宙'; |
- 使用
debugger
會在執行中停止,這時在 DevTool 裡可看到Scope
,Scope
表示函式的作用域。
以下這些參數是在運行函式時,本身就會存取的參數內容:
arguments
會把此函式的「參數」以陣列裝成一個「類陣列」。window
瀏覽器本身,屬全域變數(Global)。this
指向全域變數。
This 指向誰?
this
在上述的函式裡是指向 window,但是在使用時,它可能會有許多不同的指向可能。指向會影響到框架的使用,例如希望this
指向某個元件(物件),但卻指向 window,就會發生不預期的錯誤。
1 | var obj = { |
params
, window
, arguments
的結果和上述結果是一樣的,但是this
卻不同,this
指向了哪裡?
這次this
指向本身的物件,所以我們在 console 可以看到物件裡的屬性和方法。
複雜的 This 指向
傳統函式寫法的this
只與調用方式有關,以下使用var
來定義變數(全域),如果使用let
或const
則有不同的結果(undefined)。
使用 Simple call 的方式呼叫:
1 | var someone = '全域宇宙'; |
this 運用的變化
兩個同樣的函式callSomeone()
,一個放在全域,一個用物件包起來,來測試指向。
1 | var someone = '全域宇宙'; |
- 如果函式本身在全域環境裡,呼叫時自然是指向全域
- 如果在呼叫這個函式時,是呼叫物件裡的函式,那麼
this
就會指向包裹它的物件obj
. - 如果在物件裡呼全域外的函式,裡面的變數也會先指向物件
obj2
本身。 - 如果物件有多層,那麼在呼叫的時候還是會指向上「ㄧ」層。
- 如果是在物件裡的函式,並且直接呼叫(使用 Simple call)的方式呼叫,這時
this
仍會指回向全域的物件變數(window),而不會指向本身的物件。
通常不會使用 Simple call 的方式去調用this
,因為this
指向會不是我們想要的結果。而會使用在物件下的方式調用this
。 callback function
大部分是屬於Simple call
的形式,它的this
大多指向全域,只有少部分的callback function
會重新定義,this
指向也會不同。
待續…
概念
調用函式的方式不同, this 的指向也會不同。
1. simple call
指在調用時,直接以函式名後加雙括號的方式調用,如sayHi()
。
2. Object methods
函式本身是包含在一個物件裡,也就是是物件的方法,如果要調用則以物件的方式調用,如obj.sayHi()
。
3. 建構式 new 創建的函式
以建構式 new 創建的函式,它的this
指向,就會指向 new 這個函式的建構式的物件本身。