JavaScript 閉包機制詳解及實例代碼
首先要區(qū)分兩個概念,一是匿名函數(shù),一是閉包。
所謂匿名函數(shù),就是創(chuàng)建函數(shù)沒有給定函數(shù)名。經常出現(xiàn)的包括函數(shù)表達式,就是定義一個匿名函數(shù),然后將函數(shù)賦值給某個變量,而此時這個變量就相當于該函數(shù)的函數(shù)名,例如:
var sayHi = function(){
alert("Hi");
}; //注意這個分號
sayHi(); //調用函數(shù)
還有一種常用匿名函數(shù)的情況是回調函數(shù),如 JQuery 中常用到的:
$("p").click(function(){
alert("click");
});
此外,還有利用匿名函數(shù)作為某函數(shù)的返回值:
function sayNameWithAge(age){
return function(person){
if(person.age == age){
return person.name;
}
}
}
那么,閉包又是怎么一回事呢?所謂的閉包,其實就是一個函數(shù),而這個函數(shù)有一點比較特別,它有權能夠去訪問其他函數(shù)作用域的變量。
從定義中我們發(fā)現(xiàn),其實在上面的匿名函數(shù)例子中,就存在這樣的閉包。在最后一個例子中,匿名函數(shù)訪問了函數(shù) sayNameWithAge 的參數(shù) age,那么,這個作為返回值的匿名函數(shù)就是一個閉包。
要徹底理解閉包,就必須理解函數(shù)調用時的整個機制,這里從作用域鏈的相關知識來進行講解。
首先看下面的例子:
function sayName(name){
alert(name);
}
sayName("Jack");
在上面的函數(shù) sayName 被調用的時候,就會創(chuàng)建一個對應的執(zhí)行環(huán)境和作用域鏈,如下圖所示:

當 sayName 函數(shù)被調用時,創(chuàng)建了相應的作用域鏈,而作用域中包含兩個引用分別指向兩個對象,其中一個是全局變量對象,這個全局對象是在函數(shù)創(chuàng)建的時候就已經創(chuàng)建了,只是在調用函數(shù)的時候才將其復制到作用域鏈中;而另一個就是函數(shù)的活動對象,這個對象是在調用函數(shù)的時候才創(chuàng)建的。
在函數(shù)中訪問一個變量時,就會從作用域中搜索對應名字的變量。
而當函數(shù)執(zhí)行完畢后,函數(shù)的活動對象會被銷毀,而全局變量對象卻永遠保存在內存中。
但是,上面所說的都是普通函數(shù)的情況,對于閉包而言,又是另外一種情況:
以上面的 sayNameWithAge 函數(shù)為例:
function sayNameWithAge(age){
return function(person){
if(person.age == age){
return person.name;
}
}
}
//創(chuàng)建函數(shù)
var sayName = sayNameWithAge(18);
//調用函數(shù)
var name = sayName({name:"Jack",age:18});
//解除對匿名函數(shù)的引用
sayName = null;
當上面的 sayName 函數(shù)被調用的時候,產生的作用域鏈如下所示:

當匿名函數(shù)被 return 后,它的作用域鏈被創(chuàng)建,并且包含了外部函數(shù)的活動對象和全局變量對象,這樣一來,這個匿名函數(shù)就可以訪問 sayNameWithAge 函數(shù)中定義的所有變量,也就是一個閉包。
這樣的閉包會存在一個問題,就是當 sayNameWithAge 函數(shù)執(zhí)行完畢的時候(JS 的垃圾處理機制大多是標記清除),其活動對象被閉包所引用,所以活動對象并不會被銷毀,只有當匿名函數(shù)被銷毀后,sayNameWithAge 的活動對象才會被銷毀,所以上面的最后一行解除對匿名函數(shù)的引用不僅是為了銷毀閉包的對象,也是為了銷毀外部函數(shù)的活動對象。所以,慎重使用閉包!?。?/p>
關于閉包,還有一個需要注意的地方,就是在閉包中訪問其他函數(shù)的變量,實際上是因為閉包的作用域鏈中有指向其他函數(shù)的活動對象的引用,而不是閉包自身的活動對象中保存著這些變量。看下面的例子:
function outer(){
var result = new Array();
for(var i = 0; i < 5; i ++){
result[i] = function(){
return i;
};
}
return result;
}
按照設想,最后 outer 返回的數(shù)組各個項中的值應該是與其下標一致的。但是,最后的結果卻是每個項的值都是 5
不難想象,在上面的所有閉包的作用域鏈中,都有一個引用指向了 outer 的活動對象中的參數(shù) i,而且是指向同一個對象。
當 outer 函數(shù)執(zhí)行完畢的時候,i 的值是 5。也就是說,所有閉包中訪問 i 的時候取到的值都是 5
那么,我們可以通過另一種方法來實現(xiàn)預想的效果:
function outer(){
var result = new Array();
for(var i = 0; i < 5; i ++){
result[i] = (fuction(index){
return index;
})(i);
}
return result;
}
這里我們?yōu)槟涿瘮?shù)定義一個參數(shù) index,并在每次循環(huán)中立即調用該函數(shù),將 i 的當前值復制給參數(shù) index(注意 JS 中是按值傳遞),并將返回的 index 賦值給 result。
此外,閉包中需要注意的另一個問題是 this 對象。
this 對象在 JS 中是在函數(shù)運行時基于函數(shù)的執(zhí)行環(huán)境綁定的。而匿名函數(shù)的執(zhí)行環(huán)境具有全局性,也就是說,在匿名函數(shù)中,this 對象通常指向 window。
var name = "Tom";
var person = {
name : "Jack",
sayName : function(){
return (function(){
return this.name;
})();
}
}
person.sayName(); //Tom
上面在閉包中訪問 this.name,其中的 this 對象并非取得自身或是 person 的 this 對象,而是指向 window。
如果需要在閉包中訪問外部函數(shù)的 this 對象,那么,可以在外部函數(shù)中定義一個變量,將 this 對象傳給該變量。
var name = "Tom";
var person = {
name : "Jack",
sayName : function(){
var self = this;
return (function(){
return self.name;
})();
}
}
person.sayName(); //Jack
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關文章
js+html5獲取用戶地理位置信息并在Google地圖上顯示的方法
這篇文章主要介紹了js+html5獲取用戶地理位置信息并在Google地圖上顯示的方法,涉及html5元素的操作技巧,需要的朋友可以參考下2015-06-06
模仿JQuery.extend函數(shù)擴展自己對象的js代碼
最近打算寫個自己的js工具集合,把自己平常經常使用的方法很好的封裝起來,其中模仿了jq的結構。2009-12-12
JavaScript實現(xiàn)網頁端播放攝像頭實時畫面
這篇文章主要介紹了如何利用JavaScript實現(xiàn)在網頁端播放局域網(不能上云)或是廣域網的攝像頭的實時畫面,文中的示例代碼講解詳細,需要的可以參考一下2022-02-02

