如果对作用域函数为独立的对象这样的基本概念理解较好的话理解闭包的概念并在实际的编程实践中应用则颇有水到渠成之感
在DOM的事件处理方面大多数程序员甚至自己已经在使用闭包了而不自知在这种情况下对于浏览器中内嵌的JavaScript引擎的bug可能造成内存洩漏这一问题姑且不论就是程序员自己调试也常常会一头雾水
用 简单的语句来描述JavaScript中的闭包的概念由于JavaScript中函数是对象对象是属性的集合而属性的值又可以是对象则在函数内 定义函数成为理所当然如果在函数func内部声明函数inner然后在函数外部调用inner这个过程即产生了一个闭包
闭包的特性
我们先来看一个例子如果不了解JavaScript的特性很难找到原因
复制代码 代码如下:
var outter = [];
function clouseTest() {
var array = ["one"
"two"
"three"
"four"];
for (var i =
; i < array
length; i++) {
var x = {};
x
no = i;
x
text = array[i];
x
invoke = function () {
print(i);
}
outter
push(x);
}
}
//调用这个函数
clouseTest();
print(outter[
]
invoke());
print(outter[
]
invoke());
print(outter[
]
invoke());
print(outter[
]
invoke());
运行的结果如何呢?很多初学者可能会得出这样的答案
然而运行这个程序得到的结果为
其 实在每次迭代的时候这样的语句xinvoke = function(){print(i);}并没有被执行只是构建了一个函数体为”print(i);”的函数对象如此而已而当i=时迭代停 止外部函数返回当再去调用outter[]invoke()时i的值依旧为因此outter数组中的每一个元素的invoke都返回i的 值如何解决这一问题呢?我们可以声明一个匿名函数并立即执行它
复制代码 代码如下:
var outter = [];
function clouseTest
() {
var array = ["one"
"two"
"three"
"four"];
for (var i =
; i < array
length; i++) {
var x = {};
x
no = i;
x
text = array[i];
x
invoke = function (no) {
return function () {
print(no);
}
}(i);
outter
push(x);
}
}
clouseTest
();
</script>
这个例子中我们为xinvoke赋值的时候先运行一个可以返回一个函数的函数然后立即执行之这样xinvoke的每一次迭代器时相当与执行这样的语句
复制代码 代码如下:
//x ==
x
invoke = function(){print(
);}
//x ==
x
invoke = function(){print(
);}
//x ==
x
invoke = function(){print(
);}
//x ==
x
invoke = function(){print(
);}
这样就可以得到正确结果了闭包允许你引用存在于外部函数中的变量然而它并不是使用该变量创建时的值相反它使用外部函数中该变量最后的值
闭包的用途
现在闭包的概念已经清晰了我们来看看闭包的用途事实上通过使用闭包我们可以做很多事情比如模拟面向对象的代码风格更优雅更简洁的表达出代码在某些方面提升代码的执行效率
缓存
再来看一个例子设想我们有一个处理过程很耗时的函数对象每次调用都会花费很长时间那么我们就需要将计算出来的值存储起来当调用这个函数的时候首先在缓存中查找如果找不到则进行计算然后更新缓存并返回值如果找到了直接返回查找到的值即可
闭包正是可以做到这一点因为它不会释放外部的引用从而函数内部的值可以得以保留
复制代码 代码如下:
var CachedSearchBox = (function () {
var cache = {}
count = [];
return {
attachSearchBox: function (dsid) {
if (dsid in cache) {//如果结果在缓存中
return cache[dsid];//直接返回缓存中的对象
}
var fsb = document
getElementById(dsid);//新建
cache[dsid] = fsb;//更新缓存
if (count
length >
) {//保正缓存的大小<=
delete cache[count
shift()];
}
return fsb;
}
clearSearchBox: function (dsid) {
if (dsid in cache) {
cache[dsid]
clearSelection();
}
}
};
})();
var obj
= CachedSearchBox
attachSearchBox("input
");
//alert(obj
);
var obj
= CachedSearchBox
attachSearchBox("input
");
实现封装
复制代码 代码如下:
var person = function(){
//变量作用域为函数内部
外部无法访问
var name = "default";
return {
getName : function(){
return name;
}
setName : function(newName){
name = newName;
}
}
}();
print(person
name);//直接访问
结果为undefined
print(person
getName());
person
setName("jack");
print(person
getName());
得到结果如下
undefined
default
jack
闭包的另一个重要用途是实现面向对象中的对象传统的对象语言都提供类的模板机制这样不同的对象(类的实例)拥有独立的成员及状态互不干涉虽然JavaScript中没有类这样的机制但是通过使用闭包我们可以模拟出这样的机制还是以上边的例子来讲
复制代码 代码如下:
function Person(){
var name = "default";
return {
getName : function(){
return name;
}
setName : function(newName){
name = newName;
}
}
};
var john = Person();
print(john
getName());
john
setName("john");
print(john
getName());
var jack = Person();
print(jack
getName());
jack
setName("jack");
print(jack
getName());
运行结果如下
default
john
default
jack
javascript闭包应该注意的问题
内存洩漏
在 不同的JavaScript解释器实现中由于解释器本身的缺陷使用闭包可能造成内存洩漏内存洩漏是比较严重的问题会严重影响浏览器的响应速度降 低用户体验甚至会造成浏览器无响应等现象JavaScript的解释器都具备垃圾回收机制一般采用的是引用计数的形式如果一个对象的引用计数为 零则垃圾回收机制会将其回收这个过程是自动的但是有了闭包的概念之后这个过程就变得复杂起来了在闭包中因为局部的变量可能在将来的某些时刻 需要被使用因此垃圾回收机制不会处理这些被外部引用到的局部变量而如果出现循环引用即对象A引用BB引用C而C又引用到A这样的情况使得垃圾 回收机制得出其引用计数不为零的结论从而造成内存洩漏
上下文的引用
复制代码 代码如下:
$(function(){
var con = $("div#panel");
this
id = "content";
con
click(function(){
alert(this
id);//panel
});
});
此处的alert(thisid)到底引用着什么值呢?很多开发者可能会根据闭包的概念做出错误的判断
content
理 由是thisid显示的被赋值为content而在click回调中形成的闭包会引用到thisid因此返回值为content然而事实 上这个alert会弹出”panel”究其原因就是此处的this虽然闭包可以引用局部变量但是涉及到this的时候情况就有些微妙了因为 调用对象的存在使得当闭包被调用时(当这个panel的click事件发生时)此处的this引用的是con这个jQuery对象而匿名函数中的 thisid = “content”是对匿名函数本身做的操作两个this引用的并非同一个对象
如果想要在事件处理函数中访问这个值我们必须做一些改变
复制代码 代码如下:
$(function(){
var con = $("div#panel");
this
id = "content";
var self = this;
con
click(function(){
alert(self
id);//content
});
});
这样我们在事件处理函数中保存的是外部的一个局部变量self的引用而并非this这种技巧在实际应用中多有应用我们在后边的章节里进行详细讨论关于闭包的更多内容我们将在第九章详细讨论包括讨论其他命令式语言中的“闭包”闭包在实际项目中的应用等等
附由于本身水平有限文中难免有纰漏错误等或者语言本身有不妥当之处欢迎及时指正提出建议本文只为抛砖引玉谢谢大家!