[JavaScriptWeird]No.62 Closure 是什麼

前言

結束了關於 hoisting 的學習,接下來要討論的是 Closure 閉包,之前在奇怪部分也有紀錄到關於閉包的部分,但我期待接下來的幾節能帶給我不同的切入點,讓自己更了解閉包。

Closure 閉包

閉包也是個經常會被問到的問題,但我們在此先不要提理論,先使用一個簡單的例子來觀察閉包可以做些什麼事。

1
2
3
4
5
6
7
8
9
function test(){  
var a = 10;
function inner(){
a++;
console.log(a);
}
inner();
}
test();

這是一個結果顯而易見的程式,答案是 11 。

但是如果不在 test 函式內呼叫 inner() ,而使用 return 回傳 inner 呢?

在這之前我們要先複習一個小概念,函式呼叫。

因為有沒有加上 () 是完全不同的兩件事。

函式呼叫 ( Funtion Invocation )

  • 表示執行或者呼叫一個函式,在 JavaScript 我們用括號來表示這件事
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function test(){  
    var a = 10;
    function inner(){
    a++;
    console.log(a);
    }
    return inner;
    }

    var func = test();
    func(); // 11
    func(); // 12

使用 return 回傳 inner 函式,需要用一個變數來指向這個函式,方便後續使用它,也因為回傳的是函式,所以能直接加上 括號 () 執行,最後一樣能得到相同的結果 11 。

而神奇的是變數 a 的值會保留,原因之後再提。

如果不想額外宣告一個變數,也可以這麼做:

1
2
3
4
5
6
7
8
9
10
function test(){  
var a = 10;
function inner(){
a++;
console.log(a);
}
return inner;
}

test()(); // 11

意思是當 test() 執行完畢 return inner 時,馬上執行 inner 函式。

當寫習慣之後,可以簡短成這樣

1
2
3
4
5
6
7
8
9
10
11
function test(){  
var a = 10;
return function(){
a++;
console.log(a);
}
}

var func = test();
func(); // 11
func(); // 12

因為目的就是回傳被包在 test 函式內的那個函式,所以可以使用匿名函式的技巧表示被包在內部的函式。

目前觀察到的特性

  • 首先觀察到,我們把本來在函式內部執行的另一個函式拉出來,變成在外面呼叫這個函式。
  • 與先前的函式寫法不同,函式應該是執行完就釋放掉了,而這樣的做法,宣告在外層函式內變數指向的值會保留,似乎像是被鎖在函式內。

總結目前階段所認知的閉包

呃 … 大概就是一個函式內又回傳另一個函式 ?

以目前認知的閉包特性可以做些什麼事?

有個情境是這樣的,我們用一個函式做重複的事情,例如複雜的計算,那麼可以這麼寫:

1
2
3
4
5
6
7
function openIcebox(item){  
console.log('打開冰箱看到');
return item;
}

console.log(openIcebox('蘋果'));
console.log(openIcebox('蘋果'));

相當容易,不是嗎?

但是這麼做,每次呼叫都會執行一次 openIcebox 函式

這麼做就好比

  • 小美請小明打開冰箱,確認裡面是否有一顆蘋果?
  • 小明打開冰箱後確實看到了蘋果,並回答小美,有一顆蘋果在冰箱內。
  • 接著大熊也請小明打開冰箱,確認裡面是否有一顆蘋果?
  • 但小明金魚腦已經忘記了,所以小明又跑到冰箱前打開門看到了蘋果,並回答大熊,有一顆蘋果在冰箱內。

像小明這麼金魚腦的人,每次問相同的問題,就必須打開冰箱門確認一次,這樣是不是很浪費電?

所以小明需要一張便條紙把這些記起來。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function openIcebox(item){  
console.log('打開冰箱看到');
return item;
}

function haveMemo(func){
var memo = '';
return function(item){
if (memo){
console.log('便條紙上寫著冰箱內有');
return memo;
}
memo = func(item);
return item;
}
}

var ming = haveMemo(openIcebox);
console.log(ming('蘋果'));
console.log(ming('蘋果'));
console.log(ming('蘋果'));

對於金魚腦的小明來說,函式 haveMemo 內的變數 memo 就是小明的便條紙,因此上面那個小故事,我們可以想成:

  • 小美請小明打開冰箱,確認裡面是否有一顆蘋果?
  • 小明打開冰箱,看到蘋果並寫在便條紙上,隨後回答小美冰箱有蘋果。
  • 接著大熊也請小明打開冰箱,確認裡面是否有一顆蘋果?
  • 小明這時拿出便條紙,肯定的說冰箱內有蘋果,所以不用跑到冰箱前打開確認了。

透過這樣子的比喻,方便了解透過閉包的特性可以做到什麼樣的事情,而從這個比喻了解到,小明節省了反覆開冰箱浪費的電力以及自身的體力。回到程式來說,透過這樣子的設計,能夠讓程式的效能更好。

為什麼說是設計

  • 因為笨的方法一樣能達成目的,只是小明可能會很累。而透過一連串巧妙的設計(如小明的便條紙),可以使用聰明的方法達到同樣的目的。
  • 而閉包我想就是一種設計,如果能巧妙地運用,相信能讓程式效能更好。

以上就是對於閉包 Closure 的初步概念,接下來我們要探討原理的部分。

0%