JavaScript 程式執行原理:Closure


Posted by backas36 on 2021-08-16

Closure

有了以上的概念之後,這樣就可以幫我們更理解 closure :

var a = 1
function test() {
    var b = 2
    function inner() {
        console.log(a, b) // 1 2
    }
    return inner
}
const inner = test()
inner()

global EC => 執行 => testEC => 執行 :

剛剛有上面有解釋 EC 過了,這邊就不多講了。
那當 test 執行完之後,此時 b=2 ,然後 return inner,照理來說,這個 testEC 就會消失被回收了,但是 inner.[[Scope]] = [testEC.AO, globalEC.VO] 會被留存,因為 JS 知道我們待會會使用到。
[testEC.AO, test.[[Scope]] ] = [testEC.AO, [globalEC.VO] ] 被保留起來!

好的,回傳了 inner 之後,程式遇到 inner() => 初始化 inner EC :

好的,這邊也就解釋 closure 原理 了,由此可知如果當 b = 2 換成是 b = {超級大 object},那麼這個超級大 object 就會被保留起來,但是我們可能只是需要其中的一小部分,這樣就太浪費空間了,所以使用上要小心!

還有,我覺得老師舉的例子有深刻的讓我體會到 closure 的效率,就是當我們需要重複計算複雜的計算的時候,我們可以利用 closure 的原理更有效率的執行程式:

function complex(n) {
    console.log('進入運算')
    return n*n*n
}

function cache(fn){
    var ans = {}
    return function(n) {
        if(ans[n]) {
            return ans[n]
        }
        ans[n] = fn(n)
        return ans[n]
    }
}

const cachedComplex = cache(complex)

console.log(cachedComplex(10)) // 進入運算 1000
console.log(cachedComplex(10)) // 1000
console.log(cachedComplex(10)) // 1000
console.log(cachedComplex(10)) // 1000

神奇吧,把答案放入一個變數中,這樣就不用每次都要進行運算。

另外再提一個也是在 closure 中,我經常會搞到很混亂的例子:

var arr = []
for (var i =0; i< 5; i++){
    arr[i] = function(){
        console.log(i)
    }
}

arr[0]()

我們可能會預期 arr[0]() 會 console 出 0,arr[1]() 就 console 出 1,但發現結果都是 5。

在我們學了作用域,學了 closure 之後,我們應該要很清楚的知道,為什麼答案是 5 而不是我們想的那樣。這是因為 var 的特性,我們實際上在執行 arr[0]() 的時候,此時的 i 是 global 的 i ,已經變成 5 了。

而關於此解法最簡單的就是將 var i = 0 改成 let i = 0 就可以了 。

或者是我們可以專業的使用 closure 的方式來解決:

var arr = []
for (var i =0; i< 5; i++){
    arr[i] = logN(i)
}

function logN(n) {
    return function(){
        console.log(n)
    }
}
arr[0]()

最後還有 IIFE 的方式:

var arr = []
for (var i =0; i< 5; i++){
    arr[i] = (function(n){
        return function(){
            console.log(n)
        }
    })(i)
}

arr[0]()

closure 的應用

實務上使用 closure 的時候是在想要隱藏某些資訊的時候,來讓其他人不能從外部更改值。

老師舉了一個例子我覺很不錯:

var money = 99
function add(num) {
    money += num
}
function deduct(num) {
    if(num >=10) {
        money -=10
    } else {
        money -=num
    }
}
add(1)
deduct(100)
console.log(money) //90

看似沒問題,但是如果其他人隨便在程式放上 money = -1 那 money 就會簡簡單單的變成 -1 了。

這時候就需要 closure 去把變數封裝起來預防這種行為:

function createWallet(initMoney){
    var money = initMoney
    return {
        add: function(num){
            money +=num
        },
        deduct: function(num){
            if(num >=10) {
                money -=10
            } else {
                money -=num
            }
        },
        getMoney() {
            return money
        }
    }
}

var  myWallet = createWallet(99)
myWallet .add(1)
myWallet .deduct(100)
console.log(myWallet.getMoney()) //90

這樣子改寫之後,就算有人執行 myWallet.money = -100 ,myWallet 的 money 也是不會改變的。

這個 wallet 的例子中,雖然是利用 closure 的概念去實作,但是其實已經有散發出物件導向的氣味出來了。

是的,接下來就要筆記我自己覺得困難度最高的一部份,JS 的物件導向。


#js #closure







Related Posts

metadata 、<meta> 知多少

metadata 、<meta> 知多少

【單元測試的藝術】Chap 10: 遺留程式碼

【單元測試的藝術】Chap 10: 遺留程式碼

演習課 WEEK14 (QA)

演習課 WEEK14 (QA)


Comments