引言:JavaScript中的閉包和離散數(shù)學中的閉包并沒有任何關系渊抄。
The use of the word closure here comes from abstract algebra, where a set of elements is said to be closed under an operation if applying the operation to elements in the set produces an element that is again an element of the set. The Lisp community also (unfortunately) uses the word closure to describe a totally unrelated concept.
作用域
簡單的說垫卤,作用域就是變量與函數(shù)的可訪問范圍牲阁,即作用域控制著變量與函數(shù)的可見性和生命周期癌蓖。在JavaScript中瑟押,變量的作用域有全局作用域和局部作用域兩種。
全局作用域
var num1 = 1;
function fun1 (){
num2 = 2;
}
以上三個對象num1
,num2
和fun1
均是全局作用域戳杀,這里要注意的是末定義直接賦值的變量自動聲明為擁有全局作用域疚漆;
局部作用域
function wrap(){
var obj = "我被wrap包裹起來了,wrap外部無法直接訪問到我";
function innerFun(){
//外部無法訪問我
}
}
作用域鏈
Javascript中一切皆對象熔吗,這些對象有一個[[Scope]]
屬性辆床,該屬性包含了函數(shù)被創(chuàng)建的作用域中對象的集合,這個集合被稱為函數(shù)的作用域鏈(Scope Chain)桅狠,它決定了哪些數(shù)據(jù)能被函數(shù)訪問讼载。
function add(a,b){
return a+b;
}
當函數(shù)創(chuàng)建的時候,它的[[scope]]
屬性自動添加好全局作用域
var sum = add(3,4);
當函數(shù)調(diào)用的時候中跌,會創(chuàng)建一個稱為運行期上下文(execution context)的內(nèi)部對象咨堤,z這個對象定義了函數(shù)執(zhí)行時的環(huán)境。它也有自己的作用域鏈漩符,用于標識符解析一喘,而它的作用域鏈初始化為當前運行函數(shù)的[[Scope]]所包含的對象。
在函數(shù)執(zhí)行過程中,每遇到一個變量凸克,都會經(jīng)歷一次標識符解析過程以決定從哪里獲取和存儲數(shù)據(jù)议蟆。該過程從作用域鏈頭部,也就是從活動對象開始搜索萎战,查找同名的標識符咐容,如果找到了就使用這個標識符對應的變量,如果沒找到繼續(xù)搜索作用域鏈中的下一個對象蚂维,如果搜索完所有對象(最后一個為全局對象)都未找到戳粒,則認為該標識符未定義。
閉包
閉包簡單來說就是一個函數(shù)訪問了它的外部變量虫啥。
var quo = function(status){
return {
getStatus: function(){
return status;
}
}
}
status保存在quo中蔚约,它返回了一個對象,這個對象里的方法getStatus引用了這個status變量涂籽,即getStatus函數(shù)訪問它的外部變量status苹祟;
var newValue = quo('string');//返回了一個匿名對象,被newValue引用著
newValue.getStatus();//訪問到了quo的內(nèi)部變量status
假如并沒有getStatus這個方法评雌,那么quo('sting')結束后苔咪,status自動被回收,正是因為返回的匿名對象被一個全局對象引用柳骄,那么這個匿名對象又依賴于status,所以會阻止status的釋放箕般。
例子
//錯誤方案
var test = function(nodes){
var i ;
for(i = 0;i<nodes.length;i++){
nodes[i].onclick = function(e){
alert(i);
}
}
}
匿名函數(shù)創(chuàng)建了一個閉包耐薯,那么其訪問的i是外部test函數(shù)中的i,所以每一個節(jié)點實際上引用的是同一個i丝里。
//改進方案
var test = function(nodes){
var i ;
for(i = 0;i<nodes.length;i++){
nodes[i].onclick = function(i){
return function(){
alert(i);
};
}(i);
}
}
每一個節(jié)點綁定了一個事件曲初,這個事件接收一個參數(shù),并且立即運行杯聚,傳入i臼婆,因為是按值傳遞的,所以每一次循環(huán)都會為當前i產(chǎn)生一個新的備份幌绍。
思考題目
轉載自阮一峰博客颁褂,出自《JavaScript高級程序設計》
題目一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
運行結果:The Window
解釋:object.getNameFunc()這是屬于方法調(diào)用,this指針指向的是object傀广,可以用一個變量tmp引用它的結果颁独,實際上tmp就是這個方法返回的那個匿名函數(shù)function(){return this.name;};
,此時并沒有執(zhí)行內(nèi)部代碼伪冰,執(zhí)行tmp()時誓酒,也就是object.getNameFunc()()時,屬于函數(shù)調(diào)用(另一篇博文詳解了這里贮聂,鏈接)靠柑,this指針指向window寨辩,最終返回The Window
。
題目二
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
運行結果:My Object
解釋:在調(diào)用getNameFunc()時歼冰,屬于方法調(diào)用靡狞,那么this指針指向object,把它被that引用停巷,那么返回的匿名函數(shù)中時刻保持對object的引用耍攘,很好理解。