什麼是閉包?
閉包是即使函式在其範疇之外被調用,也仍能記得並存取其範疇的能力。
聽起來有點抽象,沒關係,我們來看Code
function foo(){
var a = 2;
function bar(){
console.log(a)
}
return bar
}
var baz = foo()
baz() // 2
一般來說,函式執行完後,Javascript引擎的垃圾回收的機制會釋放不再使用的記憶體,
回到上面的code,照垃圾回收機制,foo()執行之後,它整個內層就應該消失了,
那為何後面調用bar時,依然能順利存取到foo()中的變數a呢?
這是因為bar()仍保留一個參考指向foo()內的範疇(那個參考就叫閉包),
而JS看到這種情形,會為你保留變數a,讓它不被釋放,
所以我們後面即使在範疇之外調用,才能順利取到變數a。
我們可以簡單定義一下閉包:閉包就是一個函式能取到另一個已結束函式的變數,直到取用的函式也結束為止。
閉包其實常出現在我們的程式中
其實我們的程式中或多或少都有閉包的存在,只是我們還沒學會閉包時,認不出它是閉包
像是這段看起來隨處可見的callback
function wait(message){
setTimeout(function timer() {
console.log(`say ${message}`)
}, 1000)
}
wait('hi');
事實上,如果不是閉包,wait內層早就被釋放了,
是timer()仍然有個參考指向message,wait中的變數才能夠被保存起來,不被釋放,也讓我們的程式能順利運作
只要我們常把函式當成一級函式到處傳遞,就很有可能時常發現閉包的出現。
單獨的IIFE算是閉包嗎?
var a = 2;
(function IIFE(){
console.log(a)
}())
廣義來說算,但嚴格來說不算,
因為a是經由正常的範疇查找動作找到的,並不是透過閉包。
廣義的閉包?
廣義來說所有的函式都是閉包,在JavaScript中只要有函式被建立,閉包就會自然地產生。想了解更多可以看Huli大大的文章
所有的函式都是閉包:談 JS 中的作用域與 Closure
模組
有一些編程模式也運用了閉包的概念,像是模組,
模組模式可以讓我們隱藏私有資訊,並選擇對外公開的API
function CoolModule() {
var count = 0;
var message = 'hello';
function increment() {
count += 1
console.log(count)
}
function saySomething() {
console.log(message);
}
return {
increment: increment,
saySomething: saySomething,
};
}
var foo = CoolModule()
foo.increment() // 1
foo.saySomething() // 'hello'
這段code中count和message就是我們的私有資訊,而return的物件回傳值則是我們公開的API
也可以達到資料隔離的效果
var foo = CoolModule()
var foo2 = CoolModule()
foo.increment() // 1
foo.increment() // 2
foo2.increment() // 1
行使模組模式的兩個必要條件
- 必須有一個外層函式,且它必須至少被調用一次
- 外層函式必須至少回傳一個內層函式,以形成閉包,因此得以存取/修改私有變數
Reference:
You Don't Know JS: Scope & Closures