
經過瞭解語彙範疇後,今天要來分享 JS 我好像似懂又非懂的"閉包 (Closure)"。

所以我之前的想法其實不算是非常正確的,
原因等等看一看就會明白了。
那我們平常在哪裡可以看得到?

真的沒有在唬爛,只要你有心人人都是閉包。
說這麼多先說說他的好處是什麼?
他可以不需要使用到全域變數,
在不污染環境的情況下讓外部可以修改該狀態。
有點像是你把它封裝好的包裹,然後你可以透過念能力在外部可以使用。 (最近獵人看太多xD )

聽不懂沒關係我們直接看看範例,
function foo(){
var a = '17';
function bar(){
console.log(a);
}
bar(); // '17'
}
還記得之前學的嗎?
語彙範疇的查找功能 RHS,所以 boo 查找 a 會找到外層 foo 宣告的 a。
所以他... 是閉包嗎?
如果依照我之前以為的
閉包就是 Function 包 Function
那他就是閉包,因為他的確是 Function 包 Function。
但是你反觀剛剛定義的閉包,
其實是一個函式並可以記住並且可以存取語彙範疇,並且當該函式在他的語彙範疇外執行時也可以的功能。
那看起來就不完全不是了啊,
到目前為止好像還是看不出來閉包是什麼...
那我們再改一下上面的範例
function foo(){
var a = '17';
function bar(){
console.log(a);
}
return bar; // 注意這邊沒有執行 bar 本身
}
var baz = foo();
baz(); // 2 看到了閉包的蹤影
你會發現 baz 其實執行的就是 foo Function 內層的 bar 對吧!?
而且還可以讀取到原本在該語彙範疇所宣告的 a。
那就達成了剛剛所說的。
一個函式並可以記住並且可以存取語彙範疇,並且當該函式在他的語彙範疇外執行時也可以的功能。
等等等...
之前不是有提過有垃圾收集器 (Garbage Collection),那這樣看 foo 在執行後理論上裡面的記憶體應該會被回收才是啊,
那為何在閉包中不適用呢?
原因是閉包中的內層 bar() 函式本身還是有一個參考指向到讓外層的 foo() 讓這範疇可以繼續留存,而那個參考就叫做閉包。
之前我其實踩過這個坑就是我想要用一個 for 迴圈並且加上 setTiimeout 每秒執行一次迴圈就好,
實際寫出來長這樣。
for(var i=0; i<=5 ; i++){
setTimeout(function timer(){
console.log(i);
},1000)
}
理論上看起來會是一秒執行一次對吧!?
第一圈等待一秒再接著下一圈,
但事實是...
6
6
6
6
6

Why?
看結果而言看起來就是他一路向西走,直接把迴圈執行到底然後統一都在一秒後一次 console 出來,6的由來是因為最後一次加完他 >=5 所以不執行第六次,所以才會拿到五次的 6。
(這部分牽扯到 JS 的非同步,之後有機會再說)
所以我到底該如何實作這個?
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
Nice ~ 透過閉包並且還應用到之前提的 IIFE 每次迭代來建立一個新的範疇...
每次迭代來建立一個新的範疇...
每次迭代來建立...
每次迭代...
那其實可以直接用 let 搞定對吧!?
我們只需要建立一個區塊範疇就搞定啦~
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
是不是簡單多了!?
今天先分享到這,明天來講講剩餘的一部分,
邊旅遊還要空出時間寫文章真的不容易xDD
連假是魔鬼QQ
先這樣囉~

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))