Closure 閉包
在一個 function 裡面 return 一個 function,要用一個變數去接外面那個 function,此時呼叫變數就會等於呼叫外面那個 function,然後會回傳裡面那個 function 的值
function test() {
var a = 10
function inner() {
a++
console.log(a)
}
return inner
}
var func = test()
當 return 完 inner 後,test VO 應該會被清掉,但是 inner 還是可以存取到 test VO 裡面的 a,因為 a 會被記住
應用:
用一個變數把計算過的值記起來,當遇到同樣值的時候不用重新計算,就可以直接輸出值
function caculate(num) {
console.log('caculate')
return num * num * num
}
function cache(func) {
var ans = {}
return function(num) {
if (ans[num]) {
return ans[num]
}
ans[num] = func(num)
return ans[num]
}
}
const cacheCaculate = cache(caculate)
console.log(cacheCaculate(20)) // caculate 8000
console.log(cacheCaculate(20)) // 8000
console.log(cacheCaculate(20)) // 8000
只有第一次進入計算,然後把值記下來,後面的因為已經有一樣的值,所以不用計算,直接拿記起來的值
閉包的 Scope chain 有 reference 到其他 EC 的 VO 或 AO,所以 VO、AO 不能被清除掉
Scope chain
每一個 EC 都有一個 Scope Chain,當進入一個新的 EC,就會建立一個 Scope Chain,一層一層的關係
範例執行步驟:
- 進入 global EC,進行初始化設定
globalEC:{ global VO: { v1:undefined inner:undefined test:function } scopeChain:[globalEC.VO] } test.[[Scope]] = [globalEC.VO]
- 從 global 的第一行開始執行,v1 被宣告成 10
globalEC:{ global VO: { v1:10 inner:undefined test:function } scopeChain:[globalEC.VO] } test.[[Scope]] = [globalEC.VO] === var v1 = 10
- 呼叫 test(),進入 test 的 EC,初始化設定
testEC:{ test AO:{ vTest:undefined inner:func } scopeChian:[test.AO, globalEC.VO] } inner.[[Scope]] = [test.AO, globalEC.VO] === var inner = test() (為甚麼這裡是呼叫 test(),而不是把 inner 改成 test)
- 從 test 的第一行開始執行,vTest 被宣告成 20
testEC:{ test AO:{ vTest:20 inner:func } scopeChian:[testEC.AO, globalEC.VO] } inner.[[Scope]] = [testEC.AO, globalEC.VO]
- return inner,此時 test 已經執行完畢,但是 testEC 不會被清掉,testEC.AO 會被保留起來
- inner 被宣告成某個 function
globalEC:{ global VO: { v1:10 inner:function test:function } scopeChain:[globalEC.VO] } test.[[Scope]] = [globalEC.VO] === var inner = test()
呼叫 inner(),進入 inner 的 EC,初始化設定
innerEC:{ inner AO:{ } scopeChain:[innerEC.AO, testEC.AO, globalEC.VO ] } === inner()
- 從 inner 的第一行開始執行,console.log(v1, vTest),在 inner A在 innerEC 找不到 v1, vTest,往 testEC、global EC 找
console.log(v1, vTest) // 10, 20
其他範例
var arr = []
for (var i=0; i<5; i++) {
arr[i] = (function (n) {
return function () {
console.log(n)
}
})(i)
}
arr[0]() // 0
將 var 改成 let,迴圈的每個 i 都有一個作用域
var arr = []
for (var i=0; i<5; i++) {
arr[i] = function() {
console.log(i)
}
}
arr[1]() // 5
===
var arr = []
for (let i=0; i<5; i++) {
arr[i] = function() {
console.log(i)
}
}
arr[1]() // 1
補充:
IIFE(Immediately Invoked Functions Expressions 立即呼叫函式)
把一個匿名函式用一個 ( ) 包住,後面在接一個 ( ),就可以立即呼叫這個匿名函式
(function () {
console.log('123')
})() // 輸出 123
閉包的應用
適用於計算量大的地方
可以限制一些東西,自由度較小
能改動的地方只有他想讓你改的,比較安全