經過瞭解語彙範疇後,今天要來分享 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))