上网浏览到关于JS闭包的一些知识,突然发现以前对闭包的认知有可能有很大的错误。重新整理思考一下。
(1)什么是闭包?
function outFn(){
alert("out");
function inFn(){
alert("in");
}
var b =inFn;
return b;
}
var a = new outFn();
a();
正常情况下,outFn()的外部是不能访问inFn()的,因为这涉及到一个作用域的问题。但是JS中有一种引用机制,它可以传递函数。所谓传递函数,就是把这个函数的地址赋予一个变量。然后通过这个变量来访问这个函数的地址。
所以,如果内部函数被外部(这里是outFn())的外部(这里是全局)访问了,我们就说这是一个闭包。
有两种传递方式。一种是上面那种,返回一个被赋予inFn()的地址的变量b。通过访问b就可以访问内部函数inFn()了,所以这就是为什么我们需要return b;
的原因。
还有一种方式如下:
var a;
function outFn(){
function inFn(){
alert("in");
}
a=inFn;
}
outFn();
a();
先定义一个全局变量a,然后在outFn()中把inFn()的地址赋予给a(实际上就是引用),这时变量a的值就被改变了,因为它是全局变量。此时,访问a就相当于访问inFn()了。这种方法有一个地方需要注意 ,那就是必须先调用一次outFn(),不然变量a的值是不会有任何变化的。
我觉得可以通过下面一些实验加强对引用的理解:
(1)
var a=0;
function outFn(){
function inFn(){
a++;
alert(a);
}
return inFn;
}
var b = new outFn();
var c = new outFn();
b();
b();
c();
c();
//1 2 3 4
虽然不断的创建并调用了新的实例b 和 c,但是由于inFn()里面引用的是全局变量a。因此改变的是同一个a(都是这个地址)。所以输出是递增的。
(2)
function outFn(){
var a=0;
function inFn(){
a++;
alert(a);
}
return inFn;
}
var b = new outFn();
var c = new outFn();
b();
b();
c();
c();
//1 2 1 2
由于每次的实例化都会赋予变量一个新的地址,所以b和c的a的地址是不同的。因此这两个实例互不影响。当连续两次调用b()时,由于第一次已经对变量a做出改变,所以第二次访问变量a是访问同一个地址,此时a已经变成1了。因此输出的是2。
(3)
function outFn(){
function inFn(){
var a=0;
a++;
alert(a);
}
return inFn;
}
var b = new outFn();
var c = new outFn();
b();
b();
c();
c();
// 1 1 1 1
由于这时候变量a在内部函数里面,又因为每次调用outFn()的实例时,都会创建一个新的函数inFn(),所以里面的变量a也是新的。(所谓新的,就是在新的内存地址里面)。因此每次调用输出都是1。
这就引出了闭包的一个问题:内存泄漏。
所谓内存泄漏就是变量的内存得不到回收,增加负荷。由于Javascript的垃圾回收机制是回收那些不再被引用的变量(让这些变量=null)。又因为inFn()一直在被外面的实例调用。所以变量a得不到回收。这样就会造成内存泄漏了。解决方法就是手动让这些变量变成null:
function outFn(){
function inFn(){
var a=0;
a++;
alert(a);
}
return inFn;
a=null;
}
var b = new outFn();
var c = new outFn();
b();
b();
c();
c();
// 1 1 1 1
闭包的优点:
(1)够安全。因为变量是在函数内部里面的,或者是上一层函数里面,外面没办法随便改变和访问他。
(2)变量不会被销毁。在某些时候确实需要某些变量逃避JS垃圾回收机构的回收。会发现和上面的内存泄漏有所矛盾。所以闭包是一把双刃剑。