閉包(Closure)
在理解閉包之前需要先理解
- 自由變數(全域變數)
- 作用域鍊 scope chain
- 靜態作用域 static scope(lexical scope)
- 動態作用域 dynamic scope
作用域(Scope)
「作用域就是一個變數的生存範圍,一旦出了這個範圍,就無法存取到這個變數」
範例 - 區域變數
1 | function test(){ |
這裡因為a的作用域存在於function test裡面所以無法被印出
把區域變數變成全域變數
JS 裡有一種狀況會自動產生全域變數,那就是賦值給未宣告的變數
1 | function test() { |
範例 - 全域變數
1 | var I_am_global = 123 |
這邊的變數會寫在global裡面稱為全域變數任何地方都可以存取到它
小練習
這邊test()會印出100或是200?
1 | var a = 100 |
很混淆對嗎?
但是只要明白:
- 當函式內部找不到變數使用時會往外部找
- 這邊的練習echo的外部就是global並不是test()
- 因此答案是100
靜態作用域(static scope)
代表作用域跟這個 function 在哪裡被「呼叫」一點關係都沒有,你用肉眼看程式碼的結構就可以看出來它的作用域是什麼,而且是不會變的。
以小練習的範例說明的話:
- 在test裡面另外宣告了a 並且呼叫了它
- 但是因為靜態作用域的影響 function 被宣告時就被決定了它的外部環境也就是全域變數
- 所以執行階段的出現的a 變數(test內)並沒有影響其值
- 但是如果JS採用的是動態作用域的話印出結果就會是200
閉包(Closure)
範例說明閉包
1 | var my_balance = 999 |
就算我們函式操作好每次扣的金額,但是因為變數在全域範圍,因此任何人都可以取用這個時候就可以使用閉包
1 | function getWallet() { |
這邊透過return的方式把函式包在另一個函式內部的物件內,透過這樣的方式想要修改變數my_balance就會失敗搂!這樣的使用方式就是閉包
範例說明閉包二
假設html有五個按鈕
點擊按鈕後會得出甚麼結果?
1 | var btn = document.querySelectorAll('button') |
結果是都是5,並不是預期中0,1,2,3,4
原因如下:
- 事件會從stack被丟入web API 做處理
- 所以這邊console.log(i)就已經先印出0~4了
- 迴圈跑到5的時候跳出迴圈這時候事件的部分也從event loop回來接受i的資料
- 因此印出的內容都是5
解決方式:
使用let代替var,每次跑回圈都會產生新的作用域,因此alert出來就會是想要的值了
1 | for(let i=0; i<=4; i++) { |