前言
結束了關於 hoisting 的學習,接下來要討論的是 Closure 閉包,之前在奇怪部分也有紀錄到關於閉包的部分,但我期待接下來的幾節能帶給我不同的切入點,讓自己更了解閉包。
Closure 閉包
閉包也是個經常會被問到的問題,但我們在此先不要提理論,先使用一個簡單的例子來觀察閉包可以做些什麼事。
1 | function test(){ |
這是一個結果顯而易見的程式,答案是 11 。
但是如果不在 test
函式內呼叫 inner()
,而使用 return
回傳 inner
呢?
在這之前我們要先複習一個小概念,函式呼叫。
因為有沒有加上 () 是完全不同的兩件事。
函式呼叫 ( Funtion Invocation )
- 表示執行或者呼叫一個函式,在 JavaScript 我們用括號來表示這件事
1
2
3
4
5
6
7
8
9
10
11
12function test(){
var a = 10;
function inner(){
a++;
console.log(a);
}
return inner;
}
var func = test();
func(); // 11
func(); // 12
使用 return
回傳 inner
函式,需要用一個變數來指向這個函式,方便後續使用它,也因為回傳的是函式,所以能直接加上 括號 () 執行,最後一樣能得到相同的結果 11 。
而神奇的是變數 a 的值會保留,原因之後再提。
如果不想額外宣告一個變數,也可以這麼做:
1 | function test(){ |
意思是當 test()
執行完畢 return inner
時,馬上執行 inner
函式。
當寫習慣之後,可以簡短成這樣
1 | function test(){ |
因為目的就是回傳被包在 test
函式內的那個函式,所以可以使用匿名函式的技巧表示被包在內部的函式。
目前觀察到的特性
- 首先觀察到,我們把本來在函式內部執行的另一個函式拉出來,變成在外面呼叫這個函式。
- 與先前的函式寫法不同,函式應該是執行完就釋放掉了,而這樣的做法,宣告在外層函式內變數指向的值會保留,似乎像是被鎖在函式內。
總結目前階段所認知的閉包
呃 … 大概就是一個函式內又回傳另一個函式 ?
以目前認知的閉包特性可以做些什麼事?
有個情境是這樣的,我們用一個函式做重複的事情,例如複雜的計算,那麼可以這麼寫:
1 | function openIcebox(item){ |
相當容易,不是嗎?
但是這麼做,每次呼叫都會執行一次 openIcebox 函式。
這麼做就好比
- 小美請小明打開冰箱,確認裡面是否有一顆蘋果?
- 小明打開冰箱後確實看到了蘋果,並回答小美,有一顆蘋果在冰箱內。
- 接著大熊也請小明打開冰箱,確認裡面是否有一顆蘋果?
- 但小明金魚腦已經忘記了,所以小明又跑到冰箱前打開門看到了蘋果,並回答大熊,有一顆蘋果在冰箱內。
像小明這麼金魚腦的人,每次問相同的問題,就必須打開冰箱門確認一次,這樣是不是很浪費電?
所以小明需要一張便條紙把這些記起來。
1 | function openIcebox(item){ |
對於金魚腦的小明來說,函式 haveMemo
內的變數 memo
就是小明的便條紙,因此上面那個小故事,我們可以想成:
- 小美請小明打開冰箱,確認裡面是否有一顆蘋果?
- 小明打開冰箱,看到蘋果並寫在便條紙上,隨後回答小美冰箱有蘋果。
- 接著大熊也請小明打開冰箱,確認裡面是否有一顆蘋果?
- 小明這時拿出便條紙,肯定的說冰箱內有蘋果,所以不用跑到冰箱前打開確認了。
透過這樣子的比喻,方便了解透過閉包的特性可以做到什麼樣的事情,而從這個比喻了解到,小明節省了反覆開冰箱浪費的電力以及自身的體力。回到程式來說,透過這樣子的設計,能夠讓程式的效能更好。
為什麼說是設計:
- 因為笨的方法一樣能達成目的,只是小明可能會很累。而透過一連串巧妙的設計(如小明的便條紙),可以使用聰明的方法達到同樣的目的。
- 而閉包我想就是一種設計,如果能巧妙地運用,相信能讓程式效能更好。
以上就是對於閉包 Closure 的初步概念,接下來我們要探討原理的部分。