摘要:歡迎移步我的博客閱讀理解閉包閉包是指可以包含自由未綁定到特定對象變量的代碼塊這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義局部變量。
作用域歡迎移步我的博客閱讀:《理解閉包》
閉包 是指可以包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。“閉包” 一詞來源于以下兩者的結合:要執行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環境(作用域)。
閉包的一個重點在于作用域,在 JavaScript 中變量的作用域分兩種:全局變量與局部變量,首先讓我們來了解一下:
var _global = 1; // 全局變量 function print() { var _internal = 2; // 局部變量 console.log(_global); // 1 console.log(_internal); // 2 return _internal; } print(); console.log(_global); // 1 console.log(_internal); // ReferenceError: _internal is not defined
此時我們可以看到,在函數內部是可以直接讀取全局變量的。但當我們在外部想訪問內部變量時,就會報錯,因為在函數體外部時無法訪問函數內部的變量的。
需要注意的是,當在函數內部定義變量時沒用使用 var 等聲明變量,那么它實際上會成為一個全局變量:
function print() { _internal = 2; } console.log(_internal); // 2
從內存中解釋,變量的聲明都存在棧中,而在 JavaScript 中存在垃圾回收機制(garbage collection),當一個函數執行完返回之后,它的內存會被自動回收,此時函數內部的變量都會被銷毀。
那么我們有什么方法可以保存這一內存,并且在外部訪問函數內部的變量呢 —— 閉包。
閉包在正常情況下,我們在外部時無法修改函數內部變量的值:
// 場景 1 function print(x) { var _internal = 1; console.log(_internal + 1); } print(1); // 2 // ... print(1); // 2
我們可以看到,無論 print() 調用多少次,打印的值都是 2,_internal 的值都是 1。
這是因為 JavaScript 中的垃圾回收機制,在多次調用 print() 時,每一次都需要回收前一次的內存,之后再次申請新內存,因此 _internal 無法在內存中繼續保存。
換而言之,在每次調用 print() 時都需要為其和內部的變量申請新的內存空間,第一次 _internal 的內存地址可能為 0x...1,在函數調用完成之后,這塊內存將被釋放,再次調用時 _internal 的內存地址可能就是 0x...2 了。因此它無法再內存中被保存下來。
那么我們需要在外部使用函數內部的變量時,就需要在函數內部再聲明一個函數,并將其返回:
function print() { var _internal = 1; return function log() { console.log(_internal); } } var test = print(); test(); // 1
此時,我們已經可以從外部訪問 print() 函數內部的變量了。
當我們需要對 print() 函數內部的 _internal 的值進行修改時,我們可以給它另外一個函數:
// 場景 2 var add; function print() { var _internal = 1; add = function(x) { _internal += x; } return function log() { console.log(_internal); } } var test = print(); test(); // 1 add(1); test(); // 2
經過上述可以看出,函數 print() 在經過 add() 運行之后,_internal 的值分別為 1 和 2,這就說明了 _internal 始終保存在內存中,并沒有在 var test = print(); 調用時被回收。
這是因為 print() 內的 log() 作為返回值,被賦給 test 這個全局變量,因此 log() 始終在內存中。而 log() 依賴 print() 并且可以訪問 _internal,所以 print() 也始終在內存中,而且在 var test = print(); 調用時沒有被回收。
換而言之,當 _internal 在聲明的時候分配了內存,我們可以將其內存地址表示為 0x...1,在 print() 函數被調用之后應該會被回收,但是由于上述原因,沒有被回收,它的值將繼續保留在地址為 0x...1 中。在外部可以使用指針去尋址,并取得其值。
其他例子在循環體中,我們可能遇到:
function loopA() { var arr = []; for(var i = 0; i < 10; i++) { arr[i] = function() { return i; } } return arr; } var test = loopA(); test[0](); // 10 test[1](); // 10 // ... test[9](); // 10
在上述例子中,我們需要他們執行不同的參數得到不同的值。但是一共創建了 10 次匿名函數,,他們都是共享同一個環境的。在匿名函數執行之前,循環早已完成,此時的匿名函數一局指向循環體中的最后一個值了。
解決方案 1:
在 es6 中我們可以使用 let 聲明:
function loopA() { var arr = []; for(let i = 0; i < 10; i++) { arr[i] = function() { return i; } } return arr; } var test = loopA(); test[0](); // 0 test[1](); // 1 // ... test[9](); // 9
解決方案 2:
將函數聲明放在循環體外部:
function loopA() { var arr = []; var func = function(n) { return n; } for(var i = 0; i < 10; i++) { arr[i] = func(i) } return arr; } var test = loopA(); test[0]; // 0 test[1]; // 1 test[9]; // 9
解決方案 3:
function loopA() { var arr = []; for(var i = 0; i < 10; i++) { arr[i] = (function(i) { return i; })(i) } return arr; } var test = loopA(); test[0]; // 0 test[1]; // 1 test[9]; // 9
其他解決方案請看參考
弊端內存泄漏:由于閉包會使得函數內部的變量都被保存在內存中,不會被銷毀,內存消耗很大。因此需要在退出函數之前,將不使用的變量都刪除。
會修改函數內部變量的值。
總結閉包是一種特殊的對象。它由兩部分構成:函數,以及創建該函數的環境。環境由閉包創建時在作用域中的任何局部變量組成。
如果不是因為某些特殊任務而需要閉包,在沒有必要的情況下,在其它函數中創建函數是不明智的,因為閉包對腳本性能具有負面影響,包括處理速度和內存消耗。
百度百科 - 閉包
Wikipedia - Closure
學習 Javascript 閉包(Closure)
MDN - 閉包
深入理解閉包系列第二篇——從執行環境角度看閉包
深入理解閉包系列第四篇——常見的一個循環和閉包的錯誤詳解
深入理解javascript原型和閉包(15)——閉包
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83480.html
摘要:如何在初學就理解閉包你需要接著讀下去。這樣定義閉包是函數和聲明該函數的詞法環境的組合。小結閉包在中隨處可見。閉包是中的精華部分,理解它需要具備一定的作用域執行棧的知識。 這是本系列的第 4 篇文章。 作為 JS 初學者,第一次接觸閉包的概念是因為寫出了類似下面的代碼: for (var i = 0; i < helpText.length; i++) { var item = he...
摘要:當初看這個解釋有點懵逼,理解成閉包就是函數中的函數了。里的閉包最近不滿足于只干前端的活,開始用起了。里的閉包最近在學習語言,讓我們來看一下語言里的閉包。在中,閉包特指將函數作為值返回的情況,被返回的函數引用了生成它的母函數中的變量。 本人開始接觸編程是從js開始的,當時網上很多人說閉包是難點,各種地方對閉包的解釋也是千奇百怪。如今開始接觸js以外的各種編程語言,發現不光是js,php、...
摘要:當初看這個解釋有點懵逼,理解成閉包就是函數中的函數了。里的閉包最近不滿足于只干前端的活,開始用起了。里的閉包最近在學習語言,讓我們來看一下語言里的閉包。在中,閉包特指將函數作為值返回的情況,被返回的函數引用了生成它的母函數中的變量。 本人開始接觸編程是從js開始的,當時網上很多人說閉包是難點,各種地方對閉包的解釋也是千奇百怪。如今開始接觸js以外的各種編程語言,發現不光是js,php、...
摘要:閉包在我理解是一種比較抽象的東西。所以我寫了一篇博文來方便自己理解閉包。那么現在我們可以解釋一下閉包的第一個定義在計算機科學中,閉包是引用了自由變量的函數。循環中創建閉包在我們使用的關鍵字之前,閉包的一個常見問題就出現在循環中創建閉包。 零. 前言 從我開始接觸前端時就聽說過閉包,但是一直不理解閉包究竟是什么。上網看了各種博客,大家對閉包的說法不一。閉包在我理解是一種比較抽象的東西。所...
摘要:但是閉包也不是什么復雜到不可理解的東西,簡而言之,閉包就是閉包就是函數的局部變量集合,只是這些局部變量在函數返回后會繼續存在。可惜的是,并沒有提供相關的成員和方法來訪問閉包中的局部變量。 (收藏自 技術狂) 前言:還是一篇入門文章。Javascript中有幾個非常重要的語言特性——對象、原型繼承、閉包。其中閉包 對于那些使用傳統靜態語言C/C++的程序員來說是一個新的語言特性。本文將...
閱讀 742·2021-07-25 21:37
閱讀 3654·2019-08-30 15:55
閱讀 2572·2019-08-30 15:54
閱讀 1717·2019-08-30 15:44
閱讀 3123·2019-08-30 15:44
閱讀 859·2019-08-30 15:43
閱讀 1024·2019-08-29 15:36
閱讀 3038·2019-08-29 10:58