這一次真的懂了。
簡述
讀這篇文章前,請先理解什麼是 EC 與 VO,不然你絕對看不懂,可以參考這篇:學 hoisting 之前先理解 EC 是什麼?
Scope chain 到底是怎麼產生的?
節錄幾個 ECMAScript 裡提到的重點:
- 每一個 EC 都有一個 scope chain,而 scope chain 是一個用陣列包住的各種物件。例如說:
[scopeA, scopeB, scopeC, ...]
Every execution context has associated with it a scope chain. A scope chain is a list of objects that are searched when evaluating an Identifier.
- 進入一個 EC 時,就會建立一個 scope chain
When control enters an execution context, a scope chain is created and populated with an initial set of objects,
- Function 的 scope chain 初始值是由 AO(Activation Object)和
[[Scope]]
組成的
The scope chain is initialised to contain the activation object followed by the objects in the scope chain stored in the [[Scope]] property of the Function object.
所以總結一下,當進入一個 function 時,會產生一個 EC,EC 裡面會有 scope chain ,而 scope chain 的值是自己的 AO/VO 加上 [[Scope]]
來組成,所以大概會長的像這樣:
1 | FuncEC: { |
至於 AO 是什麼?
AO 其實就跟 學 hoisting 之前先理解 EC 是什麼? 裡面提到的 VO 是 87 分像的東西,差別在於 AO 只會在 function 的 EC 產生, VO 只會在 Global EC 產生。
不過說實在它們的差異很微小,所以呢,把 AO 想成是 VO 就好了。
接下來用一段程式碼示範一下,為了方便理解,請先記住下面的遊戲規則:
[[Scope]]
= 上一個 EC 裡的 scope chain(這個比較難解釋,建議搭配下面的例子多想幾遍)- scope chain = 自己 EC 裡的 AO/VO 加上
[[Scope]]
1 | var a = 1 |
1 | // 3. 建立 hahaha 的 EC |
所以 scope chain 就是透過上面的方式來建立的。
理解閉包的最後一塊拼圖:假裝自己是 JS 引擎
這個步驟雖然比較繁瑣,但你只要走過一遍,就能理解閉包的原理了。
這邊的例子如下:
1 | var v1 = 10 |
接下來就來一行一行執行。
首先,先進入 globalEC:
1 | globalEC: { |
Line1 var v1 = 10
1 | globalEC: { |
Line9 var inner = test()
接下來會進入 test 的 EC:
1 | testEC: { |
Line3 var vTest = 20
1 | testEC: { |
Line7 return inner
執行到這一行時,照理說 testEC 就會被 JS 的 GC(Garbage Collection 垃圾回收)機制給回收掉。
但是,注意這邊是回傳,所以 inner.[[Scope]]
會被留下來,留下的內容就是 [testEC.AO, globalEC.VO]
,因此在 inner
這個 function 裡就能存取到 test 跟 global 的 AO/VO。
所以這段就是閉包的原理,因為 return
的關係,讓原本應該要被清掉的資源沒有被清掉。這也解釋了為什麼 inner 明明離開了原本的 EC 卻還有辦法存取到 test 和 global 的 AO/VO。這一段比較複雜一點,你可以多想幾次看看。
總之呢,在回傳後的狀態會變成這樣:
1 | // 此時 testEC 已經清掉了。 |
Line10 inner()
進入 inner 的 EC:
1 | innerEC: { |
Line5 console.log(vTest, v1)
這邊分兩段,先找 vTest 的值:
- 到
innerEC.AO
找,沒有找到。 - 到
testEC.AO
找,找到了,是vTest: 20
,成功印出 20。
接著找 v1 的值:
- 到
innerEC.AO
找,沒有找到。 - 到
testEC.AO
找,沒有找到。 - 到
globalEC.Vo
找,找到了,是v1: 10
,成功印出 10。
以上。