javascript 研究閉包的工作原理
閉包可以訪問建立函式時所在作用域內的全部變數,閉包有用的方法:通過閉包模擬私有變數,通過回撥函式使得程式碼更加優雅。閉包與作用域密切相關。閉包對JavaScript的作用域規則產生了直接影響。
使用閉包模擬私有變數的程式碼
console.log("------使用閉包模擬私有變數-------"); //在建構函式內部宣告變數。由於該變數的作用域在建構函式內部,因此,feints是一個私有變數 function NinjaTest() { var feints = 0; //訪問feints計劃的方法 this.getFeints = function () { return feints; } //變數feints的增值方法。由於feints是私有變數,因此,無法通過其他方法改變變數feints的值。 this.feint = function () { feints++; } } var ninjaTest1 = new NinjaTest(); //驗證我們無法直接訪問變數feints if (ninjaTest1.feints === undefined) { console.log("And the private data is inaccessible to us."); } //呼叫feints()方法,增加變數feints的值進行統計呼叫次數 ninjaTest1.feint(); //驗證已經執行了遞增 if (ninjaTest1.getFeints() === 1) { console.log("We are able to access the internal feint count."); } var ninjaTest2 = new NinjaTest(); //當通過建構函式建立ninjaTest2時,例項ninjaTest2具有獨立的和變數feints. if (ninjaTest2.getFeints() === 0) { console.log("The second ninjaTest object gets its own feints variable."); }
我們分析第一個NinjTest物件建立完成之後的程式的狀態,我們可以利用識別符號原理來更好理解這種情況之下閉包的工作原理。通過關鍵字new呼叫JavaScript建構函式。因此,每次呼叫建構函式時,都會建立一個新的詞法環境,該詞法環境儲存建構函式內部的區域性變數。在上述程式碼中,建立了NinjaTest環境,儲存對變數feints的跟蹤。
此時,無論何時建立函式,都會保持對詞法環境的引用(通過內建[[Enviroment]]屬性)。在上述程式碼中,NinjaTest建構函式內部,我們建立了兩個函式:getFeints和feint,均有NijaTest環境的引用,因為NinjaTest環境是這兩個函式建立時所處的環境。getFeints與feint函式是新建立的ninjaTest的物件方法(可以通過this關鍵字訪問)。因此,可以通過NinjaTest建構函式外部訪問getFeints與feint函式,這樣實際上就建立了包含feints變數的閉包。
如上圖所示,當再次建立一個NinjaTest例項時,將重複整個過程。如上圖所示,顯示了建立了第二個NinjaTest物件之後的程式狀態。
每一個通過NinjaTest建構函式建立的物件例項獲得了各自的方法(ninjaTest1.getFeints()和ninjaTest2.getFeints()),當呼叫建構函式時,各自的例項方法包含各自的變數。這些“私有變數”只能通過建構函式內定義的物件方法進行訪問,不允許直接訪問。
ninjaTest2.getFeints方法呼叫時發生了什麼?如下如所示:
在呼叫ninjaTest2.getFeints()時,執行環境與詞法環境的狀態。建立新的getFeints環境,該環境具有建構函式建立ninjaTest2物件時所在的環境。getFeints函式可以訪問“私有”feints變數。
在呼叫ninjaTest2.getFeints方法之前,JavaScript引擎正在執行全域性程式碼。我們的程式處於全域性執行上下文狀態,是執行棧裡的唯一上下文。同時,唯一活躍的詞法環境是全域性環境,與全域性執行上下文關聯。
當呼叫ninjaTest2.getFeints()時,我們呼叫的是ninjaTest2物件的getFeints方法。由於每次呼叫函式時均會建立新的執行上下文,因此建立了新的getFeints執行環境並推入執行棧。
這同時引起建立新的詞法環境,詞法環境通常用於保持跟蹤函式中定義的變數。另外,getFeints詞法環境包含了getFeints函式被建立時所處的環境,當ninjaTest2物件建立時,NijaTest環境是活躍的。
現在我們瞭解了檢視獲取feints變數時是如何工作的。首先,訪問活躍的getFeints詞法環境。因為在getFeints函式內部未定義任何變數,該詞法環境是空的,找不到feints變數。接下來,在當前詞法環境的外部環境進行查詢————在上述程式碼中,當建立ninjaTest2物件時,NinjaTest環境處於活躍狀態。Ninja環境中具有feints變數的引用,完成搜尋過程。
通過分析,我們理解了處理閉包時,執行上下文與詞法環境所扮演的角色,那麼接下來,我們將關注“私有”變數,為什麼要保持“私有”變數的引用。這下“私有”變數並不是物件的私有屬性,但是可以通過建構函式所建立的物件方法去訪問這些變數。
參考《JavaScript忍者祕籍》