JavaScript Closure 你一定有用過的閉包

JavaScript Closure 你一定有用過的閉包

June 27, 2020

·

5 min read

JavsScript 的 closure (閉包) 是什麼? 假設你有在寫 javascript 的話,你可能每天都在用,但你只是沒特別查覺而已。

Clousre 在 MDN 上解釋為

A closure is the combination of a function bundled together (enclosed)
with references to its surrounding state (the lexical environment).

這個解釋是我看過比較簡單直白的。

而我自己對 closure 白話解釋的話,就是利用 return 回傳值,並且做一個作用域環境封裝。

Closure 封裝變數

以下我建立一個變數 a,是一個 object value 是 { name: ‘ian’},function getValueA 則會回傳 a,javascript 特性,會在 function 建立時,就以同層尋找變數,找不到就一層一層往外。接下來建立變數 assignA 賦予 getValueA() 回傳值。

嘗試 log 出來後,會發現第一次會是 {name: “ian”},但是接下來重新賦予 a 的值,神奇的事發生了,發現 assignA 的值並沒有被改變。但嘗試直接 log 回傳 getValueA() 會發現,a 是有成功被改變了。

  • 封裝 lexical environment ( 作用域環境 )
var a = { name: 'ian' };
function getValueA() {
    return a;
}
var assignA = getValueA();
console.log(assignA); // {name: "ian"}

a = 2;
console.log(assignA); // {name: "ian"}
console.log(getValueA()); // 2

這是因為 closure 幫我們做封裝記憶體,javascript 的記憶體管理機制,幫我們把第一次 return a 時,這個 { name: ‘ian’ },封裝起來避免被記憶體回收,假設要回收 { name: ‘ian’ } 的方法就是讓 assignA 指向新的參考。

  • javascript 記憶體回收
「沒有其他任何物件參考它」。如果一個物件不在被任何物件參考,它將被視為可回收記憶體的垃圾。

MDN: 記憶體生命週期

JavaScript 作用域

下面的這個問題,相信大家面試的時候,都被問到爛了,聽到問題是 forLoop 跟 setTimeout 開頭都快可以直接反射背出答案 X D。

  • 經典 (面試) 問題
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
}
// 3
// 3
// 3

這問題又與 javascript event loop 有關,setTimeout 是 web API,在 javascript 執行緒時,碰到 setTimeout 會被特別放到另一個 stack,等到最後才會來執行,邏輯上就是 i 已經被重新更新到 3 了,但是我才要來開始 log i,那結果當然是 3 出現三次。

MDN: Event Loop

setTimeout 解法

還是簡單講一下,其中簡單的方法是 var 改用 let,讓每次 let 的作用域被包覆所以記憶起來。另一個就是利用記憶體封裝概念 ,同樣原理也與 let 差不多,利用 function logI(i) 傳入變數,來讓 i 當下的值被封裝。

for (var i = 0; i < 3; i++) {
    function logI(i) {
        setTimeout(function() {
            console.log(i);
        }, 0);
    }
    logI(i);
}
// 0
// 1
// 2

如果你將 logI function 的 argument 拿掉,又會出現三次的 3,因為沒有封裝變數 i。

...
    function logI() {
        setTimeout(function() {
            console.log(i)
        }, 0);
    }
    logI();
...
// 3
// 3
// 3

心得

稍微整理一下最近面試常會問的問題,順便整理一下自己對 closure 基本觀念。在找 setTimeout forLoop 除了 let、立即函示外的解法,才找到 function pass argument,也發現自己也不夠了解 function argument 對於 memory 機制這部分。

雖然說 react 開發上,比較少會因為 closure 踩到雷,但相信每多懂一點,未來雷就少踩一點

感謝閱讀,有錯誤或意見歡迎留言。