# 閉包(Closure) ###### tags: `Javascript` 在理解閉包之前需要先理解 * 自由變數(全域變數) * 作用域鍊 scope chain * 靜態作用域 static scope(lexical scope) * 動態作用域 dynamic scope # 作用域(Scope) > 「作用域就是一個變數的生存範圍,一旦出了這個範圍,就無法存取到這個變數」 ## 範例 - 區域變數 ```javascript= function test(){ var a = 10 } console.log(a) // 印出結果: Uncaught ReferenceError: a is not defined ``` 這裡因為a的作用域存在於function test裡面所以無法被印出 ### 把區域變數變成全域變數 JS 裡有一種狀況會自動產生全域變數,那就是賦值給未宣告的變數 ```javascript= function test() { n = 10; //n直接轉變成全域變數 console.log('hello'); }; test(); console.log(n); function test2() { console.log(n + 1); }; test2(); console.log(window.n); // 從這段可以明白n是全域變數 // hello , 10, 11, 10 ``` ## 範例 - 全域變數 ```javascript= var I_am_global = 123 function test() { console.log(I_am_global) } test() // 印出結果:123 ``` 這邊的變數會寫在 global 裡面稱為全域變數任何地方都可以存取到它 ## 小練習 這邊test()會印出 100 或是 200 ? ```javascript= var a = 100; function echo() { console.log(a) // 100 or 200? }; function test() { var a = 200; echo(); }; test(); ``` 很混淆對嗎? 但是只要明白: 1. 當函式內部找不到變數使用時會往外部找 2. 這邊的練習 echo 的外部就是 global 並不是 `test()` 裏面 3. 因此答案是 100 # 靜態作用域(static scope) > 代表作用域跟這個 function 在哪裡被「呼叫」一點關係都沒有,你用肉眼看程式碼的結構,作用域在哪邊就是哪邊,並且不會變更。 以小練習的範例說明的話: 1. 在 test 裡面另外宣告了 a 並且呼叫了它 2. 但是因為靜態作用域的影響 function 被宣告時就被決定了它的外部環境也就是全域變數 3. 所以執行階段的出現的 a 變數 ( test 內) 並沒有影響其值 4. 但是如果 JS 採用的是動態作用域的話印出結果就會是 200 # 閉包(Closure) ## 範例說明閉包 ```javascript= var my_balance = 999 function deduct(n) { my_balance -= (n > 10 ? 10 : n) // 超過 10 塊只扣 10 塊 } deduct(13) // 只被扣 10 塊 my_balance -= 999 // 還是被扣了 999 塊 ``` 就算我們函式操作好每次扣的金額,但是因為變數在全域範圍,因此任何人都可以取用這個時候就可以使用閉包 ```javascript= function getWallet() { var my_balance = 999 return { deduct: function(n) { my_balance -= (n > 10 ? 10 : n) // 超過 10 塊只扣 10 塊 } } }; var wallet = getWallet(); wallet.deduct(13); // 只被扣 10 塊 my_balance -= 999;// Uncaught ReferenceError: my_balance is not defined ``` 這邊透過 return 的方式把函式包在另一個函式內部的物件內,透過這樣的方式想要修改變數 my_balance 就會失敗搂!這樣的使用方式就是**閉包** ## 範例說明閉包二 假設html有五個按鈕 點擊按鈕後會得出甚麼結果? ```javascript= var btn = document.querySelectorAll('button') for (var i = 0; i <= 4; i++) { console.log(i); btn[i].addEventListener('click', function () { alert(i) }) } ``` 結果是都是5,並不是預期中0,1,2,3,4 原因如下: 1. 事件會從 stack 被丟入 event queue 等待做處理 2. 所以這邊 console.log(i) 就已經先印出0~4了 3. 迴圈跑到5的時候跳出迴圈這時候事件的部分也從 event loop 回來接受i的資料 4. 因此印出的內容都是5 解決方式: 使用let代替var,每次跑回圈都會產生新的作用域,因此alert出來就會是想要的值了 ```javascript= for(let i=0; i<=4; i++) { btn[i].addEventListener('click', function() { alert(i) }) } ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up