前言
我們了解了函式是物件的一種,有屬性及許多其它的東西。記得課程剛開始時,提過的執行環境嗎?這堂課是物件、函式,以及 this 的探討。
當函式被呼叫
複習一下,當函式被呼叫時,會創造新的執行環境。當執行環境被創造,放進執行堆,這過程決定了程式怎樣執行。
每個執行環境都有自己的變數環境,也就是被創造在函式內的變數所在,並且可以參考到外部環境,能夠隨著範圍鏈一路往下找,直到全域執行環境。
而我們也知道,當函式被執行、執行環境被創造時 JavaScript 也會產生一個我們沒宣告過的特殊變數 this
。
this
this 會指向不同的物件,而這是根據函式是如何被呼叫的,這很重要,讓我們直接從簡單的範例了解吧。
直接觀察 this
1 | console.log(this); |
如果直接取用 this
,這個 this
會指向全域物件 window。
建立函式陳述句呼叫觀察 this :
1 | function a (){ |
直接呼叫 a
函式,於是函式 a
內的執行環境被創造、 this
也被創造,在這個情況下, this
會指向全域物件 window。
建立函式表示式呼叫觀察 this :
1 | var b = function(){ |
結果也是一樣的,因為我們仍然是直接呼叫變數 b
的函式。
從上面兩個小測試可以觀察到,無論我們使用表示式、陳述句在何處創造函式,並不會影響 this
指向全域物件,因為會影響到 this
的是函式如何被呼叫。
小測試
而每一個執行環境都有自己的 this
, 在上述兩個小測試中的 this
都指向同一個記憶體位址,也就是同個全域物件,所以我們可以再透過這個延伸例子觀察:
1 | function a (){ |
在 a
函式的 this
被創造之後,我們在這個 this
上利用點運算子新增一個屬性,將這個屬性連接到全域物件,所以在呼叫 a
函式之後,我們可以透過 console.log
觀察到 newVariable
的值。
可能我們會感到奇怪,為什麼取用 newVariable
變數的時候不需要使用點運算子,因為這時候的 this
指向全域物件,而任何連接到全域物件的變數都可以直接使用。這就相當於在全域執行環境時使用 var
宣告變數一樣,像這樣:
1 | function a (){ |
我們在全域執行環境中宣告了變數 c
,並且跟上面的例子一樣直接呼叫函式 a
,並在函式 a
的程式內新增全域物件的屬性,接著觀察 window
的輸出。
可以發現如果 this
指向全域物件時,使用點運算子增加屬性到全域物件上,這時的效果會同於直接在全域執行環境上使用 var
宣告變數。
物件實體內的方法
透過上面的範例,我們已經了解函式表示式、陳述句,因此這次我們在一個物件內建立一個函式。記得我們先前提的,物件是許多名稱 / 值配對而成的組合,當值是純值時稱為「屬性」;當值為函式時稱為「方法」。像這樣:
1 | var c = { |
現在情況就有點不一樣了,並不是直接呼叫函式,而是呼叫被創造在物件時體內的函式,因此要取用物件內的成員,必須使用點運算子,並且加上()呼叫該函式,也就是 c
物件的 log
方法。
因為呼叫的方式改變了,在這個範例中 this
會指向有 log
方法的 c
物件,因此我們可以利用這個特性,在方法內修改 c
物件的 name
屬性,像這樣:
1 | var c = { |
可以看到 name
屬性被修改了!
延伸範例
讓我們將範例混合起來觀察, this
是否仍然如我們所想的那樣:
1 | var c = { |
結果令人驚訝嗎?其實並不,這是可以解釋的。
在 log
方法內,我們雖然又新增了一個 setName
的函式,並且是直接的呼叫它,但是會影響 this
的是函式呼叫的方式,並非實際上程式碼的實體位置,因此雖然方法內的 this
是指向 c
物件本身,但在 setName
函式內的 this
仍然是指向全域物件 window
。
所以我們可以在全域物件 window
中找到剛剛新增的屬性
那麼,該如何修改才能符合預期呢?
其實很容易,我們說過物件是傳參考的,我們只需要創造一個變數,並把想保存的 this
利用等號運算子設定給該變數就可以了,像這樣:
1 | var c = { |
這樣就不需要考慮每個時候的 this
究竟是指向誰,只需要知道要保存下來的 this
是指向誰就可以了。
在這個例子中,我希望保存 this
指向 c
物件的記憶體位址,因此用了變數 self
配合等號運算子,令其與 this
指向同樣的 c
物件的記憶體位址。這樣即使之後 this
變動,也已經 self
無關,我們仍然可以使用這個變數修改 c
物件。
額外補充
關於 this 部分還有很多例子可以細細觀察,這部分可以參考 卡斯伯的鐵人賽文章 - JavaScript 的 this 到底是誰?
當然 console.log(this)
隨時查一下 this
指向哪裡也是可以的,配合著這些觀念,會讓我們寫 code 更順利哦~