ES20XX Modern JS 新語言特性筆記 | Closures
2021-08-15
筆記從 ES2015 / ES2016 / ES2017 / ES2018 / ES2019 / ES2020 以來,JS 所加入的新語言特性。使用的情境新語言特性不僅是為了減少對第三方套件的依賴 (Lodash),也是基於 ESLint 上 Clean Code、Best Practices 規範要求。熟悉新語言特性不僅能夠使 JS 開發更加快速也能夠提升閱讀性。
本篇筆記為介紹 JavaScript 令人迷惑的概念 Cloures。
說明
Closures 最早是從 Python 或者是 JavaScript 認識已經不太清楚了,但在 JS 的領域討論 Closures 的人似乎較多一些。而 Closures 的中文翻譯「閉包」更是讓人困惑,音有點像細胞,但顯然兩者沒有任何關閉。而在摸索之後,其實關於 Closures 必須要看過應用的範例,再重新思索它的定義,才能夠真正明白為什麼會翻譯為閉包:
其實閉包是有關程式語言變數作用域 (Lexical Scope) 的一個程式概念,閉包是將函式以及其所屬環境資訊記憶起來,以提供外部使用。藉由閉包可以達到函式的重複使用,以及在 JS 中以替代的方式設計物件導向設系中的 Private Method。
使用範例
function foo(){
let count = 0;
bar = function(){
return ++count;
}
return bar
}
f = foo()
f2 = foo()
f() // 1
f() // 2
f2() // 1
f() // 3
f2() //2
由於 f() 及 f2() 有著不同的環境資訊 count,所以呼叫函式得到的結果不同。
實際應用 | Reuse Function
首先定義函式為 Closure 的設計方式,讓程式碼可以被重複使用。
function exponential(power){
return function(base){
return base ** power;
}
}
接著可以重複利用程式碼,專門計算平方、三次、四次方的函數:
square = exponential(2)
toTheThirdPower = exponential(3)
toTheFourthPower = exponential(4)
使用方式:
square(3)
// 9
toTheThirdPower(3)
// 27
toTheFourthPower(3)
// 81
實際應用 | Private Method
...
setTimeout 在作用域上的經典問題
for (var index = 1; index <= 3; index++) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
}
如果使用 var 會每間隔 1 秒回應一次下列訊息
after 4 seconds(s):4
after 4 seconds(s):4
after 4 seconds(s):4
原因在於 for 與 setTimeout 的搭配作用順序如下:
- index 設為 1
- 呼叫 setTimeout() 執行時間為 1000 ms 後
- index 設定 2
- 呼叫 setTimeout() 執行時間為 2000 ms 後
- index 設定 3
- 呼叫 setTimeout() 執行時間為 3000 ms 後
- index 設定 4
以上的動作對於 JS Runtime 來說飛快可以執行完成。
而在 for-loop 中的 setTimeout callback function 會共享 global scope 的 index。
- 當時間為 1000ms 時,使用 callback function,closure,顯示為 4
- 當時間為 2000ms 時,使用 callback function,closure,顯示為 4
- 當時間為 3000ms 時,使用 callback function,closure,顯示為 4
改為使用 ES6 的 let 則能夠讓產生新的 lexical scope 於每一個 callback function,因此對於 setTimeout callback function 而言,每一個 index 都是獨立的。
for (let index = 1; index <= 3; index++) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
}
after 1 seconds(s):1
after 2 seconds(s):2
after 3 seconds(s):3
中所收到的 callback function 會有 closure 的行為記憶處其環境資訊,因此如果是以 var 的方式宣告 index,當 callback 要執行時,此時
The reason you see the same message after 4 seconds is that the callback passed to the setTimeout() a closure. It remembers the value of i from the last iteration of the loop, which is 4.
In addition, all three closures created by the for-loop share the same global scope access the same value of i.
參考資料
Variable scope, closure | JAVASCRIPT.INFO
Node.js Dancing with ES20XX
查詢 Node.js 各版本對於 Ecma JavaScript 的支援情況。目前主要使用的 Node.js 12.10 對於 ES2019 以前都有相當高的支援情況,所以使用新 Feature 無煩惱。