Closure 閉包


Posted by chihyu on 2021-01-25

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,一層一層的關係

範例執行步驟:

  1. 進入 global EC,進行初始化設定
    globalEC:{
     global VO: {
         v1:undefined
         inner:undefined
         test:function
     }
     scopeChain:[globalEC.VO]
    }
    test.[[Scope]] = [globalEC.VO]
    
  2. 從 global 的第一行開始執行,v1 被宣告成 10
    globalEC:{
     global VO: {
         v1:10
         inner:undefined
         test:function
     }
     scopeChain:[globalEC.VO]
    }
    test.[[Scope]] = [globalEC.VO]
    ===
    var v1 = 10
    
  3. 呼叫 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)
    
  4. 從 test 的第一行開始執行,vTest 被宣告成 20
    testEC:{
     test AO:{
         vTest:20
         inner:func
     }
     scopeChian:[testEC.AO, globalEC.VO]
    }
    inner.[[Scope]] = [testEC.AO, globalEC.VO]
    
  5. return inner,此時 test 已經執行完畢,但是 testEC 不會被清掉,testEC.AO 會被保留起來
  6. inner 被宣告成某個 function
    globalEC:{
     global VO: {
         v1:10
         inner:function
         test:function
     }
     scopeChain:[globalEC.VO]
    }
    test.[[Scope]] = [globalEC.VO]
    ===
    var inner = test()
    
  7. 呼叫 inner(),進入 inner 的 EC,初始化設定

    innerEC:{
     inner AO:{
    
     }
     scopeChain:[innerEC.AO, testEC.AO, globalEC.VO ]
    }
    ===
    inner()
    
  8. 從 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

閉包的應用

適用於計算量大的地方
可以限制一些東西,自由度較小
能改動的地方只有他想讓你改的,比較安全


#Web #javascript #closure







Related Posts

[ week8 ] 同步非同步週-作業

[ week8 ] 同步非同步週-作業

Redux basic

Redux basic

11. Facade

11. Facade


Comments