JavaScript 閉包機(jī)制詳解及實(shí)例代碼
首先要區(qū)分兩個(gè)概念,一是匿名函數(shù),一是閉包。
所謂匿名函數(shù),就是創(chuàng)建函數(shù)沒有給定函數(shù)名。經(jīng)常出現(xiàn)的包括函數(shù)表達(dá)式,就是定義一個(gè)匿名函數(shù),然后將函數(shù)賦值給某個(gè)變量,而此時(shí)這個(gè)變量就相當(dāng)于該函數(shù)的函數(shù)名,例如:
var sayHi = function(){ alert("Hi"); }; //注意這個(gè)分號(hào) sayHi(); //調(diào)用函數(shù)
還有一種常用匿名函數(shù)的情況是回調(diào)函數(shù),如 JQuery 中常用到的:
$("p").click(function(){ alert("click"); });
此外,還有利用匿名函數(shù)作為某函數(shù)的返回值:
function sayNameWithAge(age){ return function(person){ if(person.age == age){ return person.name; } } }
那么,閉包又是怎么一回事呢?所謂的閉包,其實(shí)就是一個(gè)函數(shù),而這個(gè)函數(shù)有一點(diǎn)比較特別,它有權(quán)能夠去訪問其他函數(shù)作用域的變量。
從定義中我們發(fā)現(xiàn),其實(shí)在上面的匿名函數(shù)例子中,就存在這樣的閉包。在最后一個(gè)例子中,匿名函數(shù)訪問了函數(shù) sayNameWithAge 的參數(shù) age,那么,這個(gè)作為返回值的匿名函數(shù)就是一個(gè)閉包。
要徹底理解閉包,就必須理解函數(shù)調(diào)用時(shí)的整個(gè)機(jī)制,這里從作用域鏈的相關(guān)知識(shí)來進(jìn)行講解。
首先看下面的例子:
function sayName(name){ alert(name); } sayName("Jack");
在上面的函數(shù) sayName 被調(diào)用的時(shí)候,就會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的執(zhí)行環(huán)境和作用域鏈,如下圖所示:
當(dāng) sayName 函數(shù)被調(diào)用時(shí),創(chuàng)建了相應(yīng)的作用域鏈,而作用域中包含兩個(gè)引用分別指向兩個(gè)對(duì)象,其中一個(gè)是全局變量對(duì)象,這個(gè)全局對(duì)象是在函數(shù)創(chuàng)建的時(shí)候就已經(jīng)創(chuàng)建了,只是在調(diào)用函數(shù)的時(shí)候才將其復(fù)制到作用域鏈中;而另一個(gè)就是函數(shù)的活動(dòng)對(duì)象,這個(gè)對(duì)象是在調(diào)用函數(shù)的時(shí)候才創(chuàng)建的。
在函數(shù)中訪問一個(gè)變量時(shí),就會(huì)從作用域中搜索對(duì)應(yīng)名字的變量。
而當(dāng)函數(shù)執(zhí)行完畢后,函數(shù)的活動(dòng)對(duì)象會(huì)被銷毀,而全局變量對(duì)象卻永遠(yuǎn)保存在內(nèi)存中。
但是,上面所說的都是普通函數(shù)的情況,對(duì)于閉包而言,又是另外一種情況:
以上面的 sayNameWithAge 函數(shù)為例:
function sayNameWithAge(age){ return function(person){ if(person.age == age){ return person.name; } } } //創(chuàng)建函數(shù) var sayName = sayNameWithAge(18); //調(diào)用函數(shù) var name = sayName({name:"Jack",age:18}); //解除對(duì)匿名函數(shù)的引用 sayName = null;
當(dāng)上面的 sayName 函數(shù)被調(diào)用的時(shí)候,產(chǎn)生的作用域鏈如下所示:
當(dāng)匿名函數(shù)被 return 后,它的作用域鏈被創(chuàng)建,并且包含了外部函數(shù)的活動(dòng)對(duì)象和全局變量對(duì)象,這樣一來,這個(gè)匿名函數(shù)就可以訪問 sayNameWithAge 函數(shù)中定義的所有變量,也就是一個(gè)閉包。
這樣的閉包會(huì)存在一個(gè)問題,就是當(dāng) sayNameWithAge 函數(shù)執(zhí)行完畢的時(shí)候(JS 的垃圾處理機(jī)制大多是標(biāo)記清除),其活動(dòng)對(duì)象被閉包所引用,所以活動(dòng)對(duì)象并不會(huì)被銷毀,只有當(dāng)匿名函數(shù)被銷毀后,sayNameWithAge 的活動(dòng)對(duì)象才會(huì)被銷毀,所以上面的最后一行解除對(duì)匿名函數(shù)的引用不僅是為了銷毀閉包的對(duì)象,也是為了銷毀外部函數(shù)的活動(dòng)對(duì)象。所以,慎重使用閉包?。?!
關(guān)于閉包,還有一個(gè)需要注意的地方,就是在閉包中訪問其他函數(shù)的變量,實(shí)際上是因?yàn)殚]包的作用域鏈中有指向其他函數(shù)的活動(dòng)對(duì)象的引用,而不是閉包自身的活動(dòng)對(duì)象中保存著這些變量??聪旅娴睦樱?/p>
function outer(){ var result = new Array(); for(var i = 0; i < 5; i ++){ result[i] = function(){ return i; }; } return result; }
按照設(shè)想,最后 outer 返回的數(shù)組各個(gè)項(xiàng)中的值應(yīng)該是與其下標(biāo)一致的。但是,最后的結(jié)果卻是每個(gè)項(xiàng)的值都是 5
不難想象,在上面的所有閉包的作用域鏈中,都有一個(gè)引用指向了 outer 的活動(dòng)對(duì)象中的參數(shù) i,而且是指向同一個(gè)對(duì)象。
當(dāng) outer 函數(shù)執(zhí)行完畢的時(shí)候,i 的值是 5。也就是說,所有閉包中訪問 i 的時(shí)候取到的值都是 5
那么,我們可以通過另一種方法來實(shí)現(xiàn)預(yù)想的效果:
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ù)定義一個(gè)參數(shù) index,并在每次循環(huán)中立即調(diào)用該函數(shù),將 i 的當(dāng)前值復(fù)制給參數(shù) index(注意 JS 中是按值傳遞),并將返回的 index 賦值給 result。
此外,閉包中需要注意的另一個(gè)問題是 this 對(duì)象。
this 對(duì)象在 JS 中是在函數(shù)運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定的。而匿名函數(shù)的執(zhí)行環(huán)境具有全局性,也就是說,在匿名函數(shù)中,this 對(duì)象通常指向 window。
var name = "Tom"; var person = { name : "Jack", sayName : function(){ return (function(){ return this.name; })(); } } person.sayName(); //Tom
上面在閉包中訪問 this.name,其中的 this 對(duì)象并非取得自身或是 person 的 this 對(duì)象,而是指向 window。
如果需要在閉包中訪問外部函數(shù)的 this 對(duì)象,那么,可以在外部函數(shù)中定義一個(gè)變量,將 this 對(duì)象傳給該變量。
var name = "Tom"; var person = { name : "Jack", sayName : function(){ var self = this; return (function(){ return self.name; })(); } } person.sayName(); //Jack
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- JavaScript閉包和范圍實(shí)例詳解
- JS作用域閉包、預(yù)解釋和this關(guān)鍵字綜合實(shí)例解析
- js閉包用法實(shí)例詳解
- 深入理解javascript函數(shù)參數(shù)與閉包
- 幾句話帶你理解JS中的this、閉包、原型鏈
- 學(xué)習(xí)Javascript閉包(Closure)知識(shí)
- JS延遲加載(setTimeout) JS最后加載
- jquery插件lazyload.js延遲加載圖片的使用方法
- JS頁面延遲執(zhí)行一些方法(整理)
- Jquery圖片延遲加載插件jquery.lazyload.js的使用方法
- Js實(shí)現(xiàn)手機(jī)發(fā)送驗(yàn)證碼時(shí)按鈕延遲操作
- JS閉包與延遲求值用法示例
相關(guān)文章
js+html5獲取用戶地理位置信息并在Google地圖上顯示的方法
這篇文章主要介紹了js+html5獲取用戶地理位置信息并在Google地圖上顯示的方法,涉及html5元素的操作技巧,需要的朋友可以參考下2015-06-06模仿JQuery.extend函數(shù)擴(kuò)展自己對(duì)象的js代碼
最近打算寫個(gè)自己的js工具集合,把自己平常經(jīng)常使用的方法很好的封裝起來,其中模仿了jq的結(jié)構(gòu)。2009-12-12用js統(tǒng)計(jì)用戶下載網(wǎng)頁所需時(shí)間的腳本
下面的方法是個(gè)不錯(cuò)的思路,建議對(duì)于js感興趣的朋友,推薦看2008-10-10

localStorage設(shè)置有效期和過期時(shí)間的簡(jiǎn)單方法

JavaScript實(shí)現(xiàn)網(wǎng)頁端播放攝像頭實(shí)時(shí)畫面