前端開發(fā)必須知道的JS之閉包及應(yīng)用
更新時間:2010年07月06日 12:58:33 作者:
本文講的是函數(shù)閉包,不涉及對象閉包(如用with實現(xiàn))。如果你覺得我說的有偏差,歡迎拍磚,歡迎指教。
在前端開發(fā)必須知道的JS之原型和繼承一文中說過下面寫篇閉包,加之最近越來越發(fā)現(xiàn)需要加強我的閉包應(yīng)用能力,所以此文不能再拖了。本文講的是函數(shù)閉包,不涉及對象閉包(如用with實現(xiàn))。如果你覺得我說的有偏差,歡迎拍磚,歡迎指教。
一. 閉包的理論
首先必須了解以下幾個概念:
執(zhí)行環(huán)境
每調(diào)用一個函數(shù)時(執(zhí)行函數(shù)時),系統(tǒng)會為該函數(shù)創(chuàng)建一個封閉的局部的運行環(huán)境,即該函數(shù)的執(zhí)行環(huán)境。函數(shù)總是在自己的執(zhí)行環(huán)境中執(zhí)行,如讀寫局部變量、函數(shù)參數(shù)、運行內(nèi)部邏輯。創(chuàng)建執(zhí)行環(huán)境的過程包含了創(chuàng)建函數(shù)的作用域,函數(shù)也是在自己的作用域下執(zhí)行的。從另一個角度說,每個函數(shù)執(zhí)行環(huán)境都有一個作用域鏈,子函數(shù)的作用域鏈包括它的父函數(shù)的作用域鏈。關(guān)于作用域、作用域鏈請看下面。
作用域、作用域鏈、調(diào)用對象
函數(shù)作用域分為詞法作用域和動態(tài)作用域。
詞法作用域是函數(shù)定義時的作用域,即靜態(tài)作用域。當(dāng)一個函數(shù)定義時,他的詞法作用域就確定了,詞法作用域說明的是在函數(shù)結(jié)構(gòu)的嵌套關(guān)系下,函數(shù)作用的范圍。這個時候也就形成了該函數(shù)的作用域鏈。作用域鏈就是把這些具有嵌套層級關(guān)系的作用域串聯(lián)起來。函數(shù)的內(nèi)部[[scope]]屬性指向了該作用域鏈。
動態(tài)作用域是函數(shù)調(diào)用執(zhí)行時的作用域。當(dāng)一個函數(shù)被調(diào)用時,首先將函數(shù)內(nèi)部[[scope]]屬性指向了函數(shù)的作用域鏈,然后會創(chuàng)建一個調(diào)用對象,并用該調(diào)用對象記錄函數(shù)參數(shù)和函數(shù)的局部變量,將其置于作用域鏈頂部。動態(tài)作用域就是通過把該調(diào)用對象加到作用域鏈的頂部來創(chuàng)建的,此時的[[scope]]除了具有定義時的作用域鏈,還具有了調(diào)用時創(chuàng)建的調(diào)用對象。換句話說,執(zhí)行環(huán)境下的作用域等于該函數(shù)定義時就確定的作用域鏈加上該函數(shù)剛剛創(chuàng)建的調(diào)用對象,從而也形成了新的作用域鏈。所以說是動態(tài)的作用域,并且作用域鏈也隨之發(fā)生了變化。再看這里的作用域,其實是一個對象鏈,這些對象就是函數(shù)調(diào)用時創(chuàng)建的調(diào)用對象,以及他上面一層層的調(diào)用對象直到最上層的全局對象?!?
譬如全局環(huán)境下的函數(shù)A內(nèi)嵌套了一個函數(shù)B,則該函數(shù)B的作用域鏈就是:函數(shù)B的作用域—>函數(shù)A的作用域—>全局window的作用域。當(dāng)函數(shù)B調(diào)用時,尋找某標(biāo)識符,會按函數(shù)B的作用域—>函數(shù)A的作用域—>全局window的作用域去尋找,實際上是按函數(shù)B的調(diào)用對象—>函數(shù)A的調(diào)用對象—>全局對象這個順序去尋找的。也就是說當(dāng)函數(shù)調(diào)用時,函數(shù)的作用域鏈實際上是調(diào)用對象鏈。
閉包
在動態(tài)執(zhí)行環(huán)境中,數(shù)據(jù)實時地發(fā)生變化,為了保持這些非持久型變量的值,我們用閉包這種載體來存儲這些動態(tài)數(shù)據(jù)(看完下面的應(yīng)用就會很好的體會這句話)。閉包的定義:所謂“閉包”,指的是一個擁有許多變量和綁定了這些變量的環(huán)境的表達式(通常是一個函數(shù)),因而這些變量也是該表達式的一部分。
閉包就是嵌套在函數(shù)里面的內(nèi)部函數(shù),并且該內(nèi)部函數(shù)可以訪問外部函數(shù)中聲明的所有局部變量、參數(shù)和其他內(nèi)部函數(shù)。當(dāng)該內(nèi)部函數(shù)在外部函數(shù)外被調(diào)用,就生成了閉包。(實際上任何函數(shù)都是全局作用域的內(nèi)部函數(shù),都能訪問全局變量,所以都是window的閉包)
譬如下面這個例子:
<script type="text/javascript">
function f(x) {
var a = 0;
a++;
x++;
var inner = function() {
return a + x;
}
return inner;
}
var test = f(1);
alert(test());
</script>
垃圾回收機制:如果某個對象不再被引用,該對象將被回收?! ?
再結(jié)合前面所講的一些概念,在執(zhí)行var test=f(1)時創(chuàng)建了f的調(diào)用對象,這里暫且記作obj,執(zhí)行完后雖然退出了外部執(zhí)行環(huán)境,但內(nèi)部函數(shù)inner被外部函數(shù)f外面的一個變量test引用。由于外部函數(shù)創(chuàng)建的調(diào)用對象obj有一個屬性指向此內(nèi)部函數(shù),而現(xiàn)在這個內(nèi)部函數(shù)又被引用,所以調(diào)用對象obj會繼續(xù)存在,不會被垃圾回收器回收,其函數(shù)參數(shù)x和局部變量a都會在這個調(diào)用對象中得以維持。雖然調(diào)用對象不能被直接訪問,但是該調(diào)用對象已成為內(nèi)部函數(shù)作用域鏈中的一部分,可以被內(nèi)部函數(shù)訪問并修改,所以執(zhí)行test()時,可以正確訪問x和a。所以說, 當(dāng)執(zhí)行了外部函數(shù)時,生成了閉包,被引用的外部函數(shù)的變量將繼續(xù)存在。
二. 閉包的應(yīng)用
應(yīng)用1:
這個是我在用js模擬排序算法過程遇到的問題。我要輸出每一次插入排序后的數(shù)組,如果在循環(huán)中寫成
setTimeout(function() { $("proc").innerHTML += arr + "<br/>"; }, i * 500);
會發(fā)現(xiàn)每次輸出的都是最終排好序的數(shù)組,因為arr數(shù)組不會為你保留每次排序的狀態(tài)值。為了保存會不斷發(fā)生變化的數(shù)組值,我們用外面包裹一層函數(shù)來實現(xiàn)閉包,用閉包存儲這個動態(tài)數(shù)據(jù)。下面用了2種方式實現(xiàn)閉包,一種是用參數(shù)存儲數(shù)組的值,一種是用臨時變量存儲,后者必須要深拷貝。所有要通過閉包存儲非持久型變量,均可以用臨時變量或參數(shù)兩種方式實現(xiàn)。
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
應(yīng)用2:
這個是無憂上的例子(點擊這里查看原帖),為每個<li>結(jié)點綁定click事件彈出循環(huán)的索引值。起初寫成
id.onclick = function(){ alert(i); } id.onclick = function(){alert(i);}
發(fā)現(xiàn)最終彈出的都是4,而不是想要的 1、2、3,因為循環(huán)完畢后i值變成了4。為了保存i的值,同樣我們用閉包實現(xiàn):
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
(ps:var a = (function(){})(); 與 var a =new function(){}效果是一樣的,均表示自執(zhí)行函數(shù)。)
應(yīng)用3:
下面的code是緩存的應(yīng)用,catchNameArr。在匿名函數(shù)的調(diào)用對象中保存catch的值,返回的對象由于被CachedBox變量引用導(dǎo)致匿名函數(shù)的調(diào)用對象不會被回收,從而保持了catch的值??梢酝ㄟ^CachedBox.getCatch("regionId");來操作,若找不到regionId則從后臺取,catchNameArr 主要是為了防止緩存過大。
<script type="text/javascript">
var CachedBox = (function() {
var cache = {}, catchNameArr = [], catchMax = 10000;
return {
getCatch: function(name) {
if (name in cache) {
return cache[name];
}
var value = GetDataFromBackend();
cache[name] = value;
catchNameArr.push(name);
this.clearOldCatch();
return value;
},
clearOldCatch: function() {
if (catchNameArr.length > catchMax) {
delete cache[catchNameArr.shift()];
}
}
};
})();
</script>
同理,也可以用這種思想實現(xiàn)自增長的ID?! ?
<script type="text/javascript">
var GetId = (function() {
var id = 0;
return function() {
return id++;
}
})();
var newId1 = GetId();
var newId2 = GetId();
</script>
應(yīng)用4:
這個是無憂上月MM的例子(點擊這里查看原帖),用閉包實現(xiàn)程序的暫停執(zhí)行功能,還蠻創(chuàng)意的。
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
把這個作用延伸下,我想到了用他來實現(xiàn)window.confirm。
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
看了上面的這些應(yīng)用,再回到前面的一句話:在動態(tài)執(zhí)行環(huán)境中,數(shù)據(jù)實時地發(fā)生變化,為了保持這些非持久型變量的值,我們用閉包這種載體來存儲這些動態(tài)數(shù)據(jù)。這就是閉包的作用。也就說遇到需要存儲動態(tài)變化的數(shù)據(jù)或?qū)⒈换厥盏臄?shù)據(jù)時,我們可以通過外面再包裹一層函數(shù)形成閉包來解決。
當(dāng)然,閉包會導(dǎo)致很多外部函數(shù)的調(diào)用對象不能釋放,濫用閉包會使得內(nèi)存泄露,所以在頻繁生成閉包的情景下我們要估計下他帶來的副作用。
畢了。希望能對大家有所幫助。
者:JayChow
出處:http://ljchow.cnblogs.com
一. 閉包的理論
首先必須了解以下幾個概念:
執(zhí)行環(huán)境
每調(diào)用一個函數(shù)時(執(zhí)行函數(shù)時),系統(tǒng)會為該函數(shù)創(chuàng)建一個封閉的局部的運行環(huán)境,即該函數(shù)的執(zhí)行環(huán)境。函數(shù)總是在自己的執(zhí)行環(huán)境中執(zhí)行,如讀寫局部變量、函數(shù)參數(shù)、運行內(nèi)部邏輯。創(chuàng)建執(zhí)行環(huán)境的過程包含了創(chuàng)建函數(shù)的作用域,函數(shù)也是在自己的作用域下執(zhí)行的。從另一個角度說,每個函數(shù)執(zhí)行環(huán)境都有一個作用域鏈,子函數(shù)的作用域鏈包括它的父函數(shù)的作用域鏈。關(guān)于作用域、作用域鏈請看下面。
作用域、作用域鏈、調(diào)用對象
函數(shù)作用域分為詞法作用域和動態(tài)作用域。
詞法作用域是函數(shù)定義時的作用域,即靜態(tài)作用域。當(dāng)一個函數(shù)定義時,他的詞法作用域就確定了,詞法作用域說明的是在函數(shù)結(jié)構(gòu)的嵌套關(guān)系下,函數(shù)作用的范圍。這個時候也就形成了該函數(shù)的作用域鏈。作用域鏈就是把這些具有嵌套層級關(guān)系的作用域串聯(lián)起來。函數(shù)的內(nèi)部[[scope]]屬性指向了該作用域鏈。
動態(tài)作用域是函數(shù)調(diào)用執(zhí)行時的作用域。當(dāng)一個函數(shù)被調(diào)用時,首先將函數(shù)內(nèi)部[[scope]]屬性指向了函數(shù)的作用域鏈,然后會創(chuàng)建一個調(diào)用對象,并用該調(diào)用對象記錄函數(shù)參數(shù)和函數(shù)的局部變量,將其置于作用域鏈頂部。動態(tài)作用域就是通過把該調(diào)用對象加到作用域鏈的頂部來創(chuàng)建的,此時的[[scope]]除了具有定義時的作用域鏈,還具有了調(diào)用時創(chuàng)建的調(diào)用對象。換句話說,執(zhí)行環(huán)境下的作用域等于該函數(shù)定義時就確定的作用域鏈加上該函數(shù)剛剛創(chuàng)建的調(diào)用對象,從而也形成了新的作用域鏈。所以說是動態(tài)的作用域,并且作用域鏈也隨之發(fā)生了變化。再看這里的作用域,其實是一個對象鏈,這些對象就是函數(shù)調(diào)用時創(chuàng)建的調(diào)用對象,以及他上面一層層的調(diào)用對象直到最上層的全局對象?!?
譬如全局環(huán)境下的函數(shù)A內(nèi)嵌套了一個函數(shù)B,則該函數(shù)B的作用域鏈就是:函數(shù)B的作用域—>函數(shù)A的作用域—>全局window的作用域。當(dāng)函數(shù)B調(diào)用時,尋找某標(biāo)識符,會按函數(shù)B的作用域—>函數(shù)A的作用域—>全局window的作用域去尋找,實際上是按函數(shù)B的調(diào)用對象—>函數(shù)A的調(diào)用對象—>全局對象這個順序去尋找的。也就是說當(dāng)函數(shù)調(diào)用時,函數(shù)的作用域鏈實際上是調(diào)用對象鏈。
閉包
在動態(tài)執(zhí)行環(huán)境中,數(shù)據(jù)實時地發(fā)生變化,為了保持這些非持久型變量的值,我們用閉包這種載體來存儲這些動態(tài)數(shù)據(jù)(看完下面的應(yīng)用就會很好的體會這句話)。閉包的定義:所謂“閉包”,指的是一個擁有許多變量和綁定了這些變量的環(huán)境的表達式(通常是一個函數(shù)),因而這些變量也是該表達式的一部分。
閉包就是嵌套在函數(shù)里面的內(nèi)部函數(shù),并且該內(nèi)部函數(shù)可以訪問外部函數(shù)中聲明的所有局部變量、參數(shù)和其他內(nèi)部函數(shù)。當(dāng)該內(nèi)部函數(shù)在外部函數(shù)外被調(diào)用,就生成了閉包。(實際上任何函數(shù)都是全局作用域的內(nèi)部函數(shù),都能訪問全局變量,所以都是window的閉包)
譬如下面這個例子:
復(fù)制代碼 代碼如下:
<script type="text/javascript">
function f(x) {
var a = 0;
a++;
x++;
var inner = function() {
return a + x;
}
return inner;
}
var test = f(1);
alert(test());
</script>
垃圾回收機制:如果某個對象不再被引用,該對象將被回收?! ?
再結(jié)合前面所講的一些概念,在執(zhí)行var test=f(1)時創(chuàng)建了f的調(diào)用對象,這里暫且記作obj,執(zhí)行完后雖然退出了外部執(zhí)行環(huán)境,但內(nèi)部函數(shù)inner被外部函數(shù)f外面的一個變量test引用。由于外部函數(shù)創(chuàng)建的調(diào)用對象obj有一個屬性指向此內(nèi)部函數(shù),而現(xiàn)在這個內(nèi)部函數(shù)又被引用,所以調(diào)用對象obj會繼續(xù)存在,不會被垃圾回收器回收,其函數(shù)參數(shù)x和局部變量a都會在這個調(diào)用對象中得以維持。雖然調(diào)用對象不能被直接訪問,但是該調(diào)用對象已成為內(nèi)部函數(shù)作用域鏈中的一部分,可以被內(nèi)部函數(shù)訪問并修改,所以執(zhí)行test()時,可以正確訪問x和a。所以說, 當(dāng)執(zhí)行了外部函數(shù)時,生成了閉包,被引用的外部函數(shù)的變量將繼續(xù)存在。
二. 閉包的應(yīng)用
應(yīng)用1:
這個是我在用js模擬排序算法過程遇到的問題。我要輸出每一次插入排序后的數(shù)組,如果在循環(huán)中寫成
setTimeout(function() { $("proc").innerHTML += arr + "<br/>"; }, i * 500);
會發(fā)現(xiàn)每次輸出的都是最終排好序的數(shù)組,因為arr數(shù)組不會為你保留每次排序的狀態(tài)值。為了保存會不斷發(fā)生變化的數(shù)組值,我們用外面包裹一層函數(shù)來實現(xiàn)閉包,用閉包存儲這個動態(tài)數(shù)據(jù)。下面用了2種方式實現(xiàn)閉包,一種是用參數(shù)存儲數(shù)組的值,一種是用臨時變量存儲,后者必須要深拷貝。所有要通過閉包存儲非持久型變量,均可以用臨時變量或參數(shù)兩種方式實現(xiàn)。
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
應(yīng)用2:
這個是無憂上的例子(點擊這里查看原帖),為每個<li>結(jié)點綁定click事件彈出循環(huán)的索引值。起初寫成
id.onclick = function(){ alert(i); } id.onclick = function(){alert(i);}
發(fā)現(xiàn)最終彈出的都是4,而不是想要的 1、2、3,因為循環(huán)完畢后i值變成了4。為了保存i的值,同樣我們用閉包實現(xiàn):
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
(ps:var a = (function(){})(); 與 var a =new function(){}效果是一樣的,均表示自執(zhí)行函數(shù)。)
應(yīng)用3:
下面的code是緩存的應(yīng)用,catchNameArr。在匿名函數(shù)的調(diào)用對象中保存catch的值,返回的對象由于被CachedBox變量引用導(dǎo)致匿名函數(shù)的調(diào)用對象不會被回收,從而保持了catch的值??梢酝ㄟ^CachedBox.getCatch("regionId");來操作,若找不到regionId則從后臺取,catchNameArr 主要是為了防止緩存過大。
復(fù)制代碼 代碼如下:
<script type="text/javascript">
var CachedBox = (function() {
var cache = {}, catchNameArr = [], catchMax = 10000;
return {
getCatch: function(name) {
if (name in cache) {
return cache[name];
}
var value = GetDataFromBackend();
cache[name] = value;
catchNameArr.push(name);
this.clearOldCatch();
return value;
},
clearOldCatch: function() {
if (catchNameArr.length > catchMax) {
delete cache[catchNameArr.shift()];
}
}
};
})();
</script>
同理,也可以用這種思想實現(xiàn)自增長的ID?! ?
復(fù)制代碼 代碼如下:
<script type="text/javascript">
var GetId = (function() {
var id = 0;
return function() {
return id++;
}
})();
var newId1 = GetId();
var newId2 = GetId();
</script>
應(yīng)用4:
這個是無憂上月MM的例子(點擊這里查看原帖),用閉包實現(xiàn)程序的暫停執(zhí)行功能,還蠻創(chuàng)意的。
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
把這個作用延伸下,我想到了用他來實現(xiàn)window.confirm。
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
看了上面的這些應(yīng)用,再回到前面的一句話:在動態(tài)執(zhí)行環(huán)境中,數(shù)據(jù)實時地發(fā)生變化,為了保持這些非持久型變量的值,我們用閉包這種載體來存儲這些動態(tài)數(shù)據(jù)。這就是閉包的作用。也就說遇到需要存儲動態(tài)變化的數(shù)據(jù)或?qū)⒈换厥盏臄?shù)據(jù)時,我們可以通過外面再包裹一層函數(shù)形成閉包來解決。
當(dāng)然,閉包會導(dǎo)致很多外部函數(shù)的調(diào)用對象不能釋放,濫用閉包會使得內(nèi)存泄露,所以在頻繁生成閉包的情景下我們要估計下他帶來的副作用。
畢了。希望能對大家有所幫助。
者:JayChow
出處:http://ljchow.cnblogs.com
相關(guān)文章
JavaScript實現(xiàn)帶有子菜單和控件的slider輪播圖效果
本文通過實例代碼給大家介紹了基于js實現(xiàn)帶有子菜單和控件的slider輪播圖效果,本文附有圖片和示例代碼,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-11-11簡單實用的js調(diào)試logger組件實現(xiàn)代碼
開發(fā)js組件的時間調(diào)試總是麻煩的,最常用的就是用alert或者debugger來測試js的運行狀態(tài)。2010-11-11解決uni-app微信小程序input輸入框在底部時,鍵盤彈起頁面整體上移問題
問題是這樣的input?獲取焦點時會自動調(diào)起手機鍵盤,設(shè)置?:adjust-position="true",會導(dǎo)致鍵盤彈起時頁面整體上移,這篇文章主要介紹了解決uni-app微信小程序input輸入框在底部時,鍵盤彈起頁面整體上移問題,,需要的朋友可以參考下2022-08-08