[JavaScriptWeird]No.64 再次 Cosplay JavaScript 引擎

前言

接著我們繼續用類似的角度來觀察這一段閉包的程式碼。

簡單的 Closure 程式

1
2
3
4
5
6
7
8
9
10
var v1 = 10;  
function test(){
var vTest = 20;
function inner(){
console.log(v1, vTest);
}
return inner;
}
var inner = test();
inner();

首先進入創造全域執行環境階段,初始化 VO 、scopeChain 以及設定 test 函式的 [[Scope]]

接著執行程式碼:

  • 於 1273 行變數 v1 賦值為 10
  • 1274 ~ 1280 行跳過
  • 於 1281 行執行 test 函式

進入 test 函式的創造執行環境階段,初始化 AO 、scopeChain 以及設定 inner 函式的 [[Scope]]

接著逐行執行 test 函式內的程式碼:

  • 於 1275 行變數 vTest 賦值為 20
  • 1276 ~ 1278 跳過
  • 於 1279 行 return inner

此時 testEC 執行完畢,被移出執行堆。

因為 inner.[[scope]] 使用到 testEC.AO 所以 testEC.AO 不會被 JavaScript 的垃圾回收機制回收掉,因此會被保留在記憶體中。

最後回到全域執行環境,繼續運行程式碼:

  • 於 1282 行呼叫 inner()

進入 inner 函式的創造執行環境階段,初始化 AO 、scopeChain

逐行執行 inner 函式內的程式碼

  • 於 1277 行印出 log, 因 innerEC.AO 內找不到變數 v1 ,因此循著 innerEC.scopeChain 最後於 globalEC.VO 內找到,值為 10 。
    • 同理,變數 vTest 則為 20 。
  • inner 函式執行完畢,移出執行堆。

程式全部運行完畢,全域執行環境移出執行堆。

到此我們的 cosplay 就到一段落了。

所有的函式都是閉包

偷用一下聳動的殺人標題,其實這個標題是可以解釋的。

我們之前提到,閉包白話來說就是一個函式裡面回傳一個函式

而透過觀察,發現無論有無回傳, JavaScript 引擎背後紀錄的東西都是一樣的,像是沒有回傳也有像是 AO、VO、scopeChain 這些東西。

所以如果我們以這個角度「會記住這些周邊資訊的函式」來定義閉包,那就成了這次的殺人標題啦,所有的函式都是閉包,因為每個函式都會記錄這些東西。

不過一般提到閉包不會講到這種定義,一般來說都是講「一個函式內回傳一個函式」才是大家認知的閉包。

心得

至此,關於閉包的原理以及觀念已經學習完了,接著要來看日常生活中可能會遇到的作用域陷阱以及閉包可以運用在哪,我個人也是蠻關注這一塊的,畢竟學了武功就是希望能派上用場 ~ 要是學了卻不知道能用在哪也是怪怪的。

學習到現在,覺得能慢慢地看懂程式碼,了解這些程式碼在背後偷偷做些什麼,讓人有點感動也覺得踏實,勉勵自己繼續投入。

0%