javascript
浅谈JavaScript中闭包
引言
閉包可以說是JavaScript中最有特色的一個地方,很好的理解閉包是更深層次的學(xué)習(xí)JavaScript的基礎(chǔ)。這篇文章我們就來簡單的談下JavaScript下的閉包。
閉包是什么?
閉包是什么?通俗的解釋是:有權(quán)訪問另一個函數(shù)作用域中變量的函數(shù)。創(chuàng)建閉包的常見方式,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)(作為其子函數(shù))。下面我們還是以前面的一個例子來簡單介紹下:
1 //通過屬性名稱來對數(shù)組元素進(jìn)行排序 2 function createComparisonFunction(propertyName) { 3 return function (obj1, obj2) { 4 var val1 = obj1[propertyName]; 5 var val2 = obj2[propertyName]; 6 if (val1 < val2) { 7 return -1; 8 } 9 else if (val1 > val2) { 10 return 1; 11 } 12 else { 13 return 0; 14 } 15 } 16 }我們看的在這個函數(shù)中我們定義了一個匿名函數(shù),并且將匿名函數(shù)作為值返回。注意代碼地4、5行,這兩行代碼訪問了外部函數(shù)中的變量propertyName。即使這個函數(shù)返回了,或者在其他地方被調(diào)用了,我們通過這個匿名函數(shù)仍然可以訪問這個變量。這是為什么呢?想想前面在對象內(nèi)部搜索屬性的機(jī)制。很明顯,匿名函數(shù)的作用域鏈包含了createComparisonFunction的作用域鏈。這又是為什么呢?還得從函數(shù)被第一次調(diào)用發(fā)生的一些細(xì)節(jié)上進(jìn)行討論。
函數(shù)第一次調(diào)用到底發(fā)生了什么
之前介紹作用域鏈的博客中有關(guān)于這方面內(nèi)容的介紹。相信大家肯定還記憶猶新。當(dāng)一個函數(shù)第一次被調(diào)用的時候,會創(chuàng)建一個執(zhí)行環(huán)境和作用域鏈,并把作用域鏈賦值給一個特殊的內(nèi)部屬性[Scope]。然后使用this、arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象。外部函數(shù)的活動對象位于第二位,外部函數(shù)的外部函數(shù)的活動對象在第三位,直到作為作用域鏈終點的全局執(zhí)行換環(huán)境。
下面還是通過一個簡單的例子來重溫下這方面的內(nèi)容:
1 function compare(value1, value2) { 2 if (value1 < value2) { 3 return -1; 4 } else if (value1 == value2) { 5 return 0; 6 } 7 else { 8 return 1; 9 } 10 } 11 12 var result = compare(5, 10);那么,按照我們之前的描述。在執(zhí)行第12行代碼的時候,作用域鏈相關(guān)的分析應(yīng)該是這樣的。看圖:
后臺的每一個執(zhí)行環(huán)境都有一個表示變量的對象--變量對象。全局環(huán)境的變量對象始終存在,像compare函數(shù)這樣的局部環(huán)境的變量對象,只是在運(yùn)行時存在。在創(chuàng)建compare()函數(shù)的時候,會創(chuàng)建一個預(yù)先包含全局變量對象的作用域鏈。這個作用域鏈被包含在內(nèi)部的[Scope]屬性中。當(dāng)調(diào)用compare()函數(shù)的時候,會為函數(shù)創(chuàng)建一個執(zhí)行環(huán)境,然后通過復(fù)制函數(shù)的[Scope]屬性中的對象構(gòu)建起執(zhí)行環(huán)境的作用域鏈。
無論什么時候在函數(shù)中訪問一個變量時,就會從作用域鏈中搜索具有相應(yīng)名字的變量。一般來講,當(dāng)函數(shù)執(zhí)行完畢以后,局部變量對象就會被銷毀,內(nèi)存中只保存全局作用域。但是閉包讓情況變的有點不同。
? 下面我們來看下我們最開始的那個例子:
1 var compare = createComparisonFunction("name"); 2 var result = compare({ name: "Nicolas" }, { name: "Grey" });在另一個函數(shù)內(nèi)部會將包含函數(shù)(外部函數(shù))的活動對象添加到它的作用域鏈中。因此在createComparisonFunction函數(shù)內(nèi)部定義的匿名函數(shù)的作用域鏈中會將createComparisonFunction函數(shù)的變量對象包含在自己的作用域鏈中。下面這張圖很好的展示了這一點:
匿名函數(shù)的作用域鏈中引用了外部函數(shù)的變量對象(活動對象)。但是:createComparisonFunction函數(shù)執(zhí)行完以后,其活動對象也不會被銷毀。因為匿名函數(shù)的作用域鏈中還引用著createComparisonFunction的活動對象。我們也可以這樣認(rèn)為,createComparisonFunction函數(shù)執(zhí)行完以后,其作用域鏈被銷毀,但是其活動對象仍然在內(nèi)存中。所以,過度的使用閉包可能會導(dǎo)致內(nèi)存占用過高。
閉包與變量
作用域鏈的這種配置機(jī)制導(dǎo)致了一個副作用,即閉包只能取得包含函數(shù)中任意變量的最后一個值。因為閉包通過作用域鏈引用的是整個變量對象。外部函數(shù)的變量存儲在其變量對象中。下面的例子可以展示這個問題:
1 /** 2 * 閉包與變量的關(guān)系示例 3 **/ 4 function createFunctions() { 5 var result = []; 6 for (var i = 0; i < 10; i++) { 7 result[i] = function () { 8 return i; 9 } 10 } 11 return result; 12 } 13 14 var funs = createFunctions(); 15 for (var i = 0; i < 10; i++) { 16 alert(funs[i]()); //輸出10次10 17 }我們看到每一個函數(shù)都輸出10。并不是我們想象中的1-10之間的數(shù)值。因為每一個result數(shù)組引用的匿名函數(shù)內(nèi)部都包含了createFunctions函數(shù)的活動對象。循環(huán)每一次的調(diào)用,修改的都是createFunctions變量對象中的i值。最后我們調(diào)用的時候看到的只是最后的一個i的值。那么我們怎么修改,才能按預(yù)想的輸出1-10呢。問題的關(guān)鍵在于:我們?nèi)绻苊恳淮窝h(huán)的時候把i的值預(yù)存起來不就可以了嗎?看看下面的這個改進(jìn)方案:
1 function createFunctions() { 2 var result = []; 3 for (var i = 0; i < 10; i++) { 4 result[i] = (function (argument) { 5 return function () { 6 return argument; 7 } 8 })(i); 9 } 10 return result; 11 } 12 13 var funs = createFunctions(); 14 for (var i = 0; i < 10; i++) { 15 alert(funs[i]()); //輸出1-9 16 }我們通過改進(jìn)后,終于如愿的輸出了1-9。看看到底發(fā)生了什么?在代碼的第4-8行,我們看到我們創(chuàng)建了一個匿名函數(shù),并且將i的值作為參數(shù)傳遞給它,然后立即執(zhí)行這個匿名函數(shù)。這個匿名函數(shù)內(nèi)部返回了另一個匿名函數(shù),result數(shù)組中保存的匿名函數(shù)的作用域鏈里面就會有4個活動對象,分別是本身的活動對象、外部匿名函數(shù)(已執(zhí)行)的活動對象(包含傳遞的i的值,即argument)、createFunctions的活動對象、全局活動對象。下面我們在執(zhí)行返回的匿名函數(shù)時,通過作用域鏈來搜索到argument變量。每一個argument變量都是當(dāng)時執(zhí)行時傳遞的i的值。
關(guān)于this對象
在閉包中使用this值也會導(dǎo)致一些問題。this對象是在運(yùn)行時根據(jù)函數(shù)的執(zhí)行環(huán)境綁定的。在全局執(zhí)行環(huán)境中,this等于window,而當(dāng)函數(shù)作為某一個對象的方法調(diào)用時,this等于那個對象。匿名函數(shù)的執(zhí)行環(huán)境具有全局性,this的值通常等于window。但有時候,可能由于編寫閉包的方式不同,這一點可能不會那么明顯。比如下面的例子:
1 var name = "The Window"; 2 var object = { 3 name: "The Object", 4 getNameFun: function () { 5 return function () { 6 return this.name; 7 } 8 } 9 } 10 11 alert(object.getNameFun()()); //輸出The Window按照之前在作用域鏈中搜索變量的機(jī)制。輸出應(yīng)該是The Object才對。但是為什么是The Window呢?前面應(yīng)該提到過,每個函數(shù)在被調(diào)用時,其活動對象都會自動獲取兩個變量this和arguments。內(nèi)部函數(shù)在搜索這兩個變量的時候,只會搜索到其活動對象為止,因此,無法訪問外部函數(shù)的這兩個變量。不過通過簡單的修改我們可以實現(xiàn)彈出The Object的效果。請看下面的例子:
1 var name = "The Window"; 2 var object = { 3 name: "The Object", 4 getNameFun: function () { 5 var that = this; 6 return function () { 7 return that.name; 8 } 9 } 10 } 11 12 alert(object.getNameFun()()); //輸出The Object我們在返回匿名函數(shù)之前,將this保存在that變量中,作為閉包,最深層次的匿名函數(shù)在調(diào)用時,其作用域鏈中會包含getNameFun這個函數(shù)的活動對象。因此這時that還是引用object對象。我們能正常的彈出The Object。講到這里,相信大家對閉包都有一個詳細(xì)的了解了把。最后推薦大家看個網(wǎng)頁,里面有很多經(jīng)典的閉包的事例哦。http://www.oschina.net/question/28_41112。
?
轉(zhuǎn)載于:https://www.cnblogs.com/dreamGong/p/4931570.html
總結(jié)
以上是生活随笔為你收集整理的浅谈JavaScript中闭包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到自己手机碎了怎么回事
- 下一篇: 梦到好多初中同学什么意思