這些觀念從"克服 JS 的奇怪部分"影片中學習的,這系列影片真的是好東西,所謂的閉包(closure),也就是說執行環境可以將那些它所要reference的外部變數給關住、包住,即便執行環境已經消失
,此現象稱作閉包(closure),在介紹閉包之前我們先來解釋一下外部環境參照(Reference to Outer Environment)、範圍鏈(scope chain)的東西。
每個執行環境都有一個參照到他的外部環境
,所謂的外部環境參照(Reference to Outer Environment)就是指說當使用變數時,js不只會在當前的執行環境中尋找變數,也會到外部環境參照中尋找變數
,而js會依照物理位置來判斷其外部參照環境為何
。
此處函數b及a之物理位置皆在全域環境底下,如下列例子,首先我們全域執行環境中有func b,func a,let myVar,呼叫a函數以後,創建函數a的執行環境,並且他的變數環境有myVar=2,接著,呼叫b函數,其執行環境消失,離開執行堆,但是變數myVar=2並不會消失,而是在記憶體空間保留
,再來就創建函數b的執行環境,做console操作然後執行環境消失,離開執行堆,所以b的外部參照環境就是我們的全域執行環境,而a外部參照環境也是一樣。
let myVar = 1;
function b() {
console.log(myVar);
}
function a() {
let myVar = 2;
b();
}
a(); // 1
但當我們將在函數a中所宣告的變數myVar給取消,也就是把let拿掉,那我們的變數myVar就會變成全域變數
,此時結果就會變成2,儘管我們將在全域環境所設置的myVar放置在函數a後面,結果也會是一樣,我們在a所宣告的全域變數myVar就會將原本的myVar所覆蓋
。
let myVar = 1;
function b() {
console.log(myVar);
}
function a() {
myVar = 2;
b();
}
// let myVar = 1;
a(); // 2
再來我們解釋一下範圍鏈,範圍指的就是我所能取到變數的地方,而鏈是指我外部環境參照的連結,所謂範圍鏈指的就是我們需要某個執行環境內程式碼的變數
,如果他在的當前環境下找不到變數,會往外部環境找
,在執行堆中由上往下找,直到全域環境
。
而下列例子,我們將函數b包在函數a中,也就是b的物理位置在函數a中,所以當我們呼叫函數a,就會創建函數b,並且變數環境會創建一個myVar=2(會保留在記憶體空間),而後離開執行堆,並呼叫函數b,而由於函數b之物理位置位於a之中,因此他會認定函數b之外部參照環境為函數a
,而就會獲取到未消失的myVar=2,而a的外部參照環境仍為全域環境。
let myVar = 1;
function a() {
function b() {
console.log(myVar); // 2
}
let myVar = 2;
b();
}
a();
介紹完上述兩個觀念後,我們直接來介紹閉包並且使用例子,首先我們在全域環境會有兩個變數分別為greetEnglish、
greetSpanish,還有一個func makeGreeting,接著逐行執行到greetEnglish呼叫makeGreeting函數,創造其執行環境,並創造變數環境將language="en"給設置進去,而後回傳匿名函數,存入greetEnglish指向其函數,然後離開執行堆,記住我們執行環境的記憶體空間仍在(en不會消失)
,再來greetSpanish也是同樣的步驟。
此時,我有兩個不同執行環境的記憶體位置,而後,我們呼叫greetEnglish,也就是呼叫我們所回傳的函數,而後我們在創建一個匿名函數的執行環境並創建其變數環境(firstname="John", lastname="Doe"),而此時外部參考環境會指向一個執行環境,js會知道我們第一個所創建的執行環境並指向它,也就是會獲取到"en",而框起來的範圍就是我們的閉包
,而greetSpanish的步驟一樣,再次強調,每當呼叫一個函數,就會創建他的執行環境,而在裡面被創建的函數,就會指向那個執行環境、指向其記憶體空間
。
function makeGreeting(language) {
return function (firstname, lastname) {
if (language == "en") {
console.log("hi" + firstname + "" + lastname);
}
if (language == "es") {
console.log("Hola" + firstname + "" + lastname);
}
}
}
let greetEnglish = makeGreeting("en");
let greetSpanish = makeGreeting("es");
greetEnglish("John", "Doe");
greetSpanish("John", "Doe");
記得應該是IE3,有一個bug...
因為他的Javascript引擎是一個作業系統共用的獨立元件,結果使用閉包以後,Javascript引擎使用的記憶體就無法回收,造成嚴重的記憶體洩漏。
當時微軟的建議是:不要使用閉包
謝謝大大回覆,IE真ㄉ很讓人頭疼R ~~