理解Javascript的闭包《转》
原文地址: http://coolshell.cn/articles/6731.html
前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点ECMAScript语言规范来使读者可以更深入的理解闭包。
注:本文是入门文章,例子素材整理于网络,如果你是高手,欢迎针对文章提出技术性建议和意见。本文讨论的是Javascript,不想做语言对比,如果您对Javascript天生不适,请自行绕道。
什么是闭包闭包是什么?闭包是Closure,这是静态语言所不具有的一个新特性。但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是:
闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。 闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配 当在一个函数内定义另外一个函数就会产生闭包上面的第二定义是第一个补充说明,抽取第一个定义的主谓宾——闭包是函数的‘局部变量’集合。只是这个局部变量是可以在函数返回后被访问。(这个不是官方定义,但是这个定义应该更有利于你理解闭包)
做为局部变量都可以被函数内的代码访问,这个和静态语言是没有差别。闭包的差别在于局部变变量可以在函数执行结束后仍然被函数外的代码访问。这意味着函数必须返回一个指向闭包的“引用”,或将这个”引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问。当然包含这个引用的实体应该是一个对象,因为在Javascript中除了基本类型剩下的就都是对象了。可惜的是,ECMAScript并没有提供相关的成员和方法来访问闭包中的局部变量。但是在ECMAScript中,函数对象中定义的内部函数(inner function)是可以直接访问外部函数的局部变量,通过这种机制,我们就可以以如下的方式完成对闭包的访问了。
?
前面的我大致了解了Javascript闭包是什么,闭包在Javascript是怎么实现的。下面我们通过针对一些例子来帮助大家更加深入的理解闭包,下面共有5个样例,例子来自于JavaScript Closures For Dummies(镜像)。
例子1:闭包中局部变量是引用而非拷贝
function
?say667()?{
????
//?Local?variable?that?ends?up?within?closure
????
var
?num?= 666;
????
var
?sayAlert?=
function
()?{ alert(num); }
????
num++;
????
return
?sayAlert;
}
?
?var
sayAlert = say667();
sayAlert()
因此执行结果应该弹出的667而非666。
例子2:多个函数绑定同一个闭包,因为他们定义在同一个函数内。
1234567891011121314function
?setupSomeGlobals()?{
????
//?Local?variable?that?ends?up?within?closure
????
var
?num?= 666;
????
//?Store?some?references?to?functions?as?global?variables
????
gAlertNumber?=
function
()?{ alert(num); }
????
gIncreaseNumber?=
function
()?{ num++; }
????
gSetNumber?=
function
(x)?{ num?= x; }
}
setupSomeGlobals();
// 为三个全局变量赋值
gAlertNumber();
//666
gIncreaseNumber();
gAlertNumber();
// 667
gSetNumber(12);
//
gAlertNumber();
//12
例子3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包
12345678910111213141516function
?buildList(list)?{
????
var
?result?=?[];
????
for
?(
var
?i?=?0;?i?<?list.length;?i++)?{
????????
var
?item?=?
'item'
?+?list[i];
????????
result.push(?
function
()?{alert(item?+?
'?'
?+?list[i])}?);
????
}
????
return
?result;
}
?
?function
?testList()?{
????
var
?fnlist?=?buildList([1,2,3]);
????
//?using?j?only?to?help?prevent?confusion?-?could?use?i
????
for
?(
var
?j?=?0;?j?<?fnlist.length;?j++)?{
????????
fnlist[j]();
????
}
}
testList的执行结果是弹出item3 undefined窗口三次,因为这三个函数绑定了同一个闭包,而且item的值为最后计算的结果,但是当i跳出循环时i值为4,所以list[4]的结果为undefined.
例子4:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。
12345678function
?sayAlice()?{
????
var
?sayAlert?=
function
()?{ alert(alice); }
????
//?Local?variable?that?ends?up?within?closure
????
var
?alice?=
'Hello Alice'
;
????
return
?sayAlert;
}
var
helloAlice=sayAlice();
helloAlice();
执行结果是弹出”Hello Alice”的窗口。即使局部变量声明在函数sayAlert之后,局部变量仍然可以被访问到。
例子5:每次函数调用的时候创建一个新的闭包
123456789101112131415161718function
?newClosure(someNum,?someRef)?{
????
//?Local?variables?that?end?up?within?closure
????
var
?num?= someNum;
????
var
?anArray?= [1,2,3];
????
var
?ref?= someRef;
????
return
function
(x)?{
????????
num += x;
????????
anArray.push(num);
????????
alert(
'num:?'
?+ num?+
????????
'\nanArray?'
?+ anArray.toString()?+
????????
'\nref.someVar?'
?+ ref.someVar);
????
}
}
closure1=newClosure(40,{someVar:
'closure 1'
});
closure2=newClosure(1000,{someVar:
'closure 2'
});
?
?closure1(5);
// num:45 anArray[1,2,3,45] ref:'someVar closure1'
closure2(-10);
// num:990 anArray[1,2,3,990] ref:'someVar closure2'
闭包的应用Singleton 单件:
123456789101112131415var
singleton =
function
() {
????
var
privateVariable;
????
function
privateFunction(x) {
????????
...privateVariable...
????
}
?
?????
return
{
????????
firstMethod:
function
(a, b) {
????????????
...privateVariable...
????????
},
????????
secondMethod:
function
(c) {
????????????
...privateFunction()...
????????
}
????
};
}();
这个单件通过闭包来实现。通过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法1可以方法私有变量,方法2访问内部私有函数。需要注意的地方是匿名主函数结束的地方的’()’,如果没有这个’()’就不能产生单件。因为匿名函数只能返回了唯一的对象,而且不能被其他地方调用。这个就是利用闭包产生单件的方法。