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