JavaScript閉包
什麽是閉包
在JavaScript中,閉包是指有權訪問另一個函數作用域中的變量的函數。創建閉包指的就是在一個函數內部創建另一個函數。
閉包的作用
1.可以使外部作用域讀取到內部作用域的變量
在js中的作用域一般是指函數,每個作用域相當於一個執行環境,每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都保存在這個對象中。
當代碼在執行環境中執行時,會創建變量對象的一個作用域鏈,作用域鏈能對當前執行環境有權訪問的所有變量和函數進行有序的訪問(本質上是一個指向變量對象的指針列表)。在JavaScript中,這種順序是單向的,內部環境可以通過作用域鏈依次訪問所有的外部環境,但外部環境不能訪問內部環境中的任何變量和函數。表現在代碼中就是,變量會從自身所處的執行環境開始依次向外層執
使用閉包,我們可以借助變通的方式從外部執行環境中拿到內部執行環境的變量。
不使用閉包:
function f1() { var m=0; var n=1; } f1(); console.log(m); //undefined console.log(n); //undefined
使用閉包:
function f1() { var m=0; var n=1;return function(){ console.log(m); console.log(n); } } var result=f1(); result(); //m=0 n=1
2.使函數中的變量在內存中的周期可控
一般上來講,當函數執行完畢後,局部活動對象就會被銷毀。但閉包的情況有所不同,在另一個函數內部定義的函數會將包含函數(即外部函數)的活動對象也添加到它的作用域鏈中。
普通函數執行完後,他的局部活動對象就會被銷毀。
function f1() { varn=1; n++; console.log(n) } var result=f1; result(); //n=2 result(); //n=2
這裏全局變量nAdd的生成依賴於f1執行,當f1執行完成後它的局部變量即被銷毀,nAdd作為一個全局變量它的變量對象並沒有被銷毀。當f1再次執行時nAdd被重新賦值。(這裏的nAdd雖然本質上也是一個閉包,但他依賴的外部函數是一個局部變量)
function f1() { var n=1; nAdd = function() { n++; console.log(n); }; console.log(n); } f1(); //n=1 nAdd(); //n=2 nAdd(); //n=3 f1(); //n=1 nAdd(); //n=2
這裏,返回的函數實際上將他和他的外部函數f1的活動對象(當然包括局部活動對象)都賦給全局變量result了,所以這些活動對象會一直保留在內存中。(由於閉包會攜帶包含他的函數的作用域,因此會比其他函數占用更多的內存)。
function f1() { var n=1; nAdd = function() { n++; console.log(n); }; return function () { console.log(n); } } var result=f1(); result();//n=1 nAdd(); //n=2 nAdd();//n=3 result(); //n=3 nAdd();//n=4
閉包的副作用
由於閉包中的變量值引用於作用域鏈中的變量對象,這通常是最後一次生成的固定的值。
function f1() { var result=new Array(); for(var i=0;i<10;i++){ result[i]=function () { return i; }; } return result; } var array=f1(); console.log(array[5]());//10
這裏如果期望每次返回相應的索引值就出問題了,因為實際上每個函數返回的都是10,因為調用array[n]時閉包引用的是同一個變量對象,而變量對象中的i保存的是最後一次生成的值10。因此,我們應該為每個閉包創建特定的變量對象(即在閉包上一層創建特定的執行環境保存不被改變的索引值)。
function f1() { var result=new Array(); for(var i=0;i<10;i++){ result[i]=function (num) { return function () { return num; }; }(i); } return result; } var array=f1(); console.log(array[5]());//5
以下是一個典型的在開發中碰到的有關閉包的bug:
$(document).ready(function() { for (var i = 1; i < 4; i++) { $(‘<li>Print ‘ + i + ‘</li>‘) .click(function() { alert(i); }).appendTo(‘ul‘); } });
這裏點擊每個li時彈出的都是4,原因跟之前一樣,調用外層變量對象的i就是最後生成的那個值。解決辦法也是創建特定的外層變量對象副本(jQuery中也可以用each代替for)。
$(document).ready(function() { for (var i = 1; i < 4; i++) { (function (num) { $(‘<li>Print ‘ + num + ‘</li>‘) .click(function() { alert(num); }).appendTo(‘ul‘); })(i) } });
附加
有一道蠻經典的題目
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //The Window
如果單純以閉包的思維理解這個答案可能想不通,this.name檢索到的第一個值應該是object.name。我認為這題主要考察的應該是this的含義。this表示唯一的含義是當前對象所屬的執行環境,這是在函數調用時自動獲取的。object.getNameFunc()()等價於this.name,而這個this.name所處的位置使他代表的是全局對象window。window.name="The Window"。
但如果我們將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
JavaScript閉包