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