閉包
- 閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)排作。
- 創(chuàng)建閉包的常見方式米母,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)。
function createComparisonFunction(propertyName) {
return function(object1, object2){
//內(nèi)部函數(shù)的作用域鏈中包含createComparisonFunction()的作用域
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
當(dāng)某個函數(shù)被調(diào)用時矫钓,會創(chuàng)建一個執(zhí)行環(huán)境及相應(yīng)的作用域鏈要尔。然后舍杜,使用arguments 和其他命名參數(shù)的值來初始化函數(shù)的活動對象但在作用域鏈中,外部函數(shù)的活動對象始終處于第二位赵辕,外部函數(shù)的外部函數(shù)的活動對象處于第三位既绩,……直至作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境。
在另一個函數(shù)內(nèi)部定義的函數(shù)會將包含函數(shù)(即外部函數(shù))的活動對象添加到它的作用域鏈中还惠。
//創(chuàng)建函數(shù)
var compareNames = createComparisonFunction("name");
//調(diào)用函數(shù)
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除對匿名函數(shù)的引用(以便釋放內(nèi)存)
compareNames = null;
- 閉包會在父函數(shù)外部饲握,改變父函數(shù)內(nèi)部變量的值。所以蚕键,如果你把父函數(shù)當(dāng)作對象(object)使用救欧,把閉包當(dāng)作它的公用方法(PublicMethod),把內(nèi)部變量當(dāng)作它的私有屬性(private value)锣光,這時一定要小心笆怠,不要隨便改變父函數(shù)內(nèi)部變量的值。
閉包與變量
- 作用域鏈的這種配置機(jī)制引出了一個值得注意的副作用誊爹,即閉包只能取得包含函數(shù)中任何變量的最后一個值蹬刷。
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
//這個函數(shù)會返回一個函數(shù)數(shù)組,每個函數(shù)都返回10
/**因為每個函數(shù)的作用域鏈中都保存著createFunctions()函數(shù)的活動對象,所以它們引用的都是同一個變量i 频丘。當(dāng)createFunctions()函數(shù)返回后办成,變量i的值是10,此時每個函數(shù)都引用著保存變量i 的同一個變量對象搂漠,所以在每個函數(shù)內(nèi)部i 的值都是10**/
- 我們可以通過創(chuàng)建另一個匿名函數(shù)強(qiáng)制讓閉包的行為符合預(yù)期
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
//創(chuàng)建并返回了一個訪問num 的閉包
return function(){
return num;
};
}(i);//將立即執(zhí)行改匿名函數(shù)的結(jié)果賦給數(shù)組
}
return result;
}
關(guān)于this對象
- this 對象是在運(yùn)行時基于函數(shù)的執(zhí)行環(huán)境綁定的:在全局函數(shù)中迂卢,this等于window,而當(dāng)函數(shù)被作為某個對象的方法調(diào)用時桐汤,this 等于那個對象
- 匿名函數(shù)的執(zhí)行環(huán)境具有全局性而克,因此其this對象通常指向window(在通過call()或apply()改變函數(shù)執(zhí)行環(huán)境的情況下,this 就會指向其他對象惊科。)
- 但有時候由于編寫閉包的方式不同,這一點(diǎn)可能不會那么明顯.
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非嚴(yán)格模式下)
- 為什么匿名函數(shù)沒有取得其包含作用域(或外部作用域)的this 對象呢亮钦?
- 每個函數(shù)在被調(diào)用時都會自動取得兩個特殊變量:this和arguments馆截。內(nèi)部函數(shù)在搜索這兩個變量時,只會搜索到其活動對象為止蜂莉,因此永遠(yuǎn)不可能直接訪問外部函數(shù)中的這兩個變量蜡娶。
- 把外部作用域中的this對象保存在一個閉包能夠訪問到的變量里,就可以讓閉包訪問該對象了.
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
/**在定義匿名函數(shù)之前映穗,我們把this對象賦值給了一個名叫that的變量窖张。而在定義了閉包之后,閉包也可以訪問這個變量蚁滋,因為它是我們在包含函數(shù)中特意聲名的一個變量宿接。即使在函數(shù)返回之后赘淮,that 也仍然引用著object,所以調(diào)用object.getNameFunc()()就返回了"My Object"睦霎。**/
內(nèi)存泄露
- 由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中梢卸,內(nèi)存消耗很大,所以不能濫用閉包副女,否則會造成網(wǎng)頁的性能問題蛤高,在IE中可能導(dǎo)致內(nèi)存泄露。
- 如果閉包的作用域鏈中保存著一個HTML 元素碑幅,那么就意味著該元素將無法被銷毀.
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
/**由于匿名函數(shù)保存了一個對assignHandler()的活動對象的引用戴陡,因此就會導(dǎo)致無法減少element 的引用數(shù)。只要匿名函數(shù)存在沟涨,element的引用數(shù)至少也是1恤批,因此它所占用的內(nèi)存就永遠(yuǎn)不會被回收**/
- 解決方法是,在退出函數(shù)之前拷窜,將不使用的局部變量全部刪除开皿。
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
模仿塊級作用域
- javaScript 沒有塊級作用域的概念,意味著在塊語句中定義的變量篮昧,實(shí)際上是在包含
函數(shù)中而非語句中創(chuàng)建的
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //計數(shù)
}
//即使像下面這樣錯誤地重新聲明同一個變量赋荆,也不會改變它的值。
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
var i; //重新聲明變量
alert(i); //計數(shù)
}
- 匿名函數(shù)可以用來模仿塊級作用域并避免這個問題懊昨。
//用作塊級作用域(通常稱為私有作用域)的匿名函數(shù)的語法
(function(){
//這里是塊級作用域
})();
- 這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部窄潭,從而限制向全局作用域中添加過多的變量和函數(shù)。
(function(){
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1){
alert("Happy new year!");
}
})();
/**這種做法可以減少閉包占用的內(nèi)存問題酵颁,因為沒有指向匿名函數(shù)的引用嫉你。只要函
數(shù)執(zhí)行完畢,就可以立即銷毀其作用域鏈了躏惋。**/
好好學(xué)習(xí)