链式作用域:
子对象会一级级地向上寻找所有父对象的变量。所以,父对象的所有变量对子对象都是可见的,反之则不成立。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
function f1(){
var n=999;
}
alert(n); // error
闭包:
在实际开发过程中,由于种种原因是需要能够访问其他函数内的局部变量,这就是闭包
function f1(){
n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
闭包的作用与理解:
创建私有变量,避免全局变量的污染;变量长期驻扎在内存中(使用不当优点变缺点,全局变量一直处于内存中,所以尽量少使用全局变量)
看一道关于闭包的经典面试题
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);
//问:三行a,b,c的输出分别是什么?
解析:首先要明确出现的三个fun函数之间的关系,第一个fun是一个新创建具名函数,第二个fun是一个新创建的匿名函数,第三个被return出去是fun其实是第一个新建的具名函数。
a:fun(0)调用的第一个fun,后面三个a.fun(n)其实调用的都是第一次fun(0)的返回值-匿名fun函数
第一次fun(0) n为0,o没传入,所以undefined
a.fun(1) m为1 n还是0 所以匿名函数调用其实是function(1,0) 第三个fun调用第一个fun, o都是0
所以a输出 undefined,0 ,0, 0
b:fun(0).fun(1)根据a可知输出undefined ,0 ;后面继续调用fun(2) ,由于fun(0)(1)调用使得当前调用还处于第一个fun,所以fun(2)调用到了第二个匿名fun 所以return第三个fun是fun(2,1)也就是第一个fun(2,1);同理到fun(3)的时候第一个fun(3,2)
所以b输出 undefined,0 ,1, 2
c:根据a和b可知,fun(0).fun(1) 和fun(0).fun(1).fun(2) 此时fun(0).fun(1).fun(2) 只是改变了第二个匿名fun的m对于n并没有改变
所以c输出 undefined,0 ,1, 1
闭包的危害?
根据闭包的特点可以知道,闭包会声明私有变量,而私有变量会常驻内存,增大内存使用量。那关于闭包会引发内存泄漏是怎么一回事呢
内存泄漏:用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。
闭包中的变量就是需要的变量,怎么会引起内存泄漏呢,要搞清楚这个问题,我们先来看一下浏览器的GC垃圾回收机制
JavaScript垃圾回收机制原理:
现在各大浏览器通常采用的垃圾回收机制有两种方法:标记清除,引用计数。
1.标记清除
s中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在一个函数中声明一个变量,就将这个变量标记为"进入环境",从逻辑上讲,永远不能释放进入环境变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为"离开环境"。
function test(){
var a = 10; //被标记"进入环境"
var b = "hello"; //被标记"进入环境"
}
test(); //执行完毕后之后,a和b又被标记"离开环境",被回收
垃圾回收机制在运行的时候会给存储再内存中的所有变量都加上标记(可以是任何标记方式),然后,它会去掉处在环境中的变量及被环境中的变量引用的变量标记(闭包)。而在此之后剩下的带有标记的变量被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后垃圾回收机制到下一个周期运行时,将释放这些变量的内存,回收它们所占用的空间。
到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
2.引用计数
跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值赋给该变量时,这个值的引用次数就是1,如果这个值再被赋值给另一个变量,则引用次数加1。相反,如果一个变量脱离了该值的引用,则该值引用次数减1,当次数为0时,就会等待垃圾收集器的回收。
这个方式存在一个比较大的问题就是循环引用,就是说A对象包含一个指向B的指针,对象B也包含一个指向A的引用。 这就可能造成大量内存得不到回收(内存泄露),因为它们的引用次数永远不可能是 0 。早期的IE版本里(IE9-)采用是计数的垃圾回收机制,闭包导致内存泄露的一个原因就是这个算法的一个缺陷。
所以 闭包会引起内存泄漏 的限定情况是 IE9-浏览器中闭包中变量被循环引用
有兴趣了解的可以移步js闭包测试
解决方案:把循环引用中的变量设为null即可,将变量设置为null意味着切断变量与它此前引用的值之间的联系。当垃圾收集器下次运行时,就会删除这些值并回收他们占用的内存。