JavaScript 闭包
2016-11-20 本文已影响133人
艾伦先生
闭包是什么
闭包就是一个拥有变量和绑定了这些变量的环境的表达式(通常是一个函数),是一个有权访问其外部作用域中变量的函数。最常见的方式就是在某函数内部创建一个函数。
- 优点
能够读取函数内部的变量,并且让这些变量的值始终保存在内存中。使用闭包和匿名自执行函数实现模块化。 - 缺点
1.既然不释放内存,则必然会对内存的消耗很大,造成页面访问的性能问题
2.闭包会改变父函数的私有属性,在你不经意的时候
一个经典的例子
function test(){
for(var i = 0; i < 10 ; i++){
setTimeout(function(){
console.log(i)
}, 0);
}
}
test();
我相信大家都会瞬间给出答案:输出是个10,原因如下:
-
test()
执行时会创建一个运行时期的上下文,而setTimeout内部的函数会放在for循环队列之后,等到for循环执行完之后才开始执行。 - function(){console.log(i)}执行时首先会寻找函数内部的变量i。此时找不到i,再寻找test中的i
- (闭包的概念:访问函数外的变量,这些变量只有等到闭包不使用才会被销毁)此时的i值已经变为了10,所以十次执行都会输出10。
解决这个问题的方法如下
function test(){
for(var i = 0; i < 10 ; i++){
(function(li){
setTimeout(function(){
console.log(li)
}, 0);
})(i);
}
}
test();
再举一个例子
function f1(){
var n=999;
nAdd=function(){n+=1}//定义了一个全局变量,相当于setter,方便在函数外部对函数内部的变量进行操作,不是本文要说明的重点
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); //返回内部函数f2,f2访问外部函数f1的局部变量n,999
nAdd(); // f1函数声明后,产生window.nAdd
result(); // 1000,可见变量n保存在了内存中;
var result2 = f1();
result2();//999 这里的值为什么不是1000呢?带着疑问往下看
一个函数,当没有依赖关系存在时,就会有被垃圾回收机制回收的可能,但是如果像上文案例f1()那样,将内部函数(f2)作为返回值赋值给一个全局变量,则会改变这种潜在的关系。这种即使离开函数作用域仍能通过引用来调用内部函数(或变量)的事实,意味着,只要存在调用内部函数的可能,JavaScript就需要保留被该内部函数引用的函数(即所谓的外部函数)
JavaScript运行时会跟踪引用这个内部函数的所有变量,知道最后一个变量废弃,JavaScript垃圾回收机制才能释放对应的空间(将内部函数置为null)
根据上面的论述,我们来分析一下第二个例子:
- f1()函数的返回值是内部函数f2()并将返回值赋值给了全局变量result
- f2()保存有f1()的的依赖关系,导致f2()始终在内存中未被清除,可以在外部访问到f1()中的变量
- result2()执行后打印了999,而不是1000。是因为创建新的封闭环境,本质上是创建了一个新的对象,而闭包就是这个对象的实例方法
小测试
第一题:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
答案是:'The Window'
这样理解 var fun = object.getNameFunc();
这个返回的是一个function
fun = function(){
return this.name;
}
此时,fun中的this指向是window,所以this.name是The window
这道题是典型的闭包没闭上~要么用聪明的that来解决,要么使用 真·闭包
// 聪明的that
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;//这里的that现在是相当于object这个对象了
return function(){
return that.name;//所以这里面输出的是object.name,也就是"My Object"
};
}
};
alert(object.getNameFunc()());
// 真·闭包术
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : (function(){
return function(){
return this.name;
};
})()
};
alert(object.getNameFunc()); //My Object