闭包是一个老生常谈的问题,简单概括下闭包的形成的两个条件:
1、定义在函数内部
2、函数内部引用父层作用域变量
举一个最简单的例子:
function test() {
var a = 1; function hello() {
console.log(a);
}
return hello;
} var world = test();
world(); //
以上代码会在控制台输出“1”。这是什么为什么呢?函数内部变量在调用结束后一般都会销毁,以上代码在test方法调用结束后并没有被销毁,这是由于js语言本身垃圾回收导致的。众所周知,js的垃圾回收机制是引用计数,当变量在其他作用域被引用时,该变量就不会被销毁,会一直存在内存中,这样就形成了闭包。在调试代码的过程中也可以在开发者工具中看出,如下图所示:
以上可以看出,当前环境存在一个作用域,Closure(text),包含引用父层的作用域的变量a。
js函数参数传递分为数值传递和引用传递,数值传递针对参数的类型为number/string/boolean....;引用传递针对参数类型为object/array....;所以在针对引用父层参数时,要注意父层参数类型,如果为数值传递,父层参数改变并不会影响闭包执行结果,但是如果参数是引用传递,要注意闭包里的参数引用的是父层变量地址,变量发生改变,闭包的执行结果会随之改变,以下代码可以看出:
var obj = {
num: {
b: 1
}
} function test(obj) {
var a = obj.num; function hello() {
console.log(a.b);
}
return hello;
} var world = test(obj);
world(); //
obj.num.b = 2;
test(obj);
world(); //
执行结果:
再说一个经典的面试题:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
想必大家都知道这段代码的执行结果是5个1,但是有没有想过为什么会是5个1呢?
setTimeout函数比较特殊,其执行优先级要低于for循环,循环结束后才会被执行,其匿名函数类似于一个回调,匿名函数都引用了变量i,var声明的变量又不存在块级作用域,最终循环结束后,i值变为5,在回调执行过程中,会向父层作用域执行RHS查询(就是查找变量i的值是否存在),i的值目前存在于全局变量window中,但是已变成了5,所以会输出5个5。
如何让其一次性输入0,1,2,3,4呢?
其实问题的本源在于js语言中本来不存在块级作用域,for循环声明的变量i最终是在全局变量中,每次循环都会更新一次全局变量中的值,所以实现块级作用域有两种常用的方法,一是借助es6的声明变量的方法let,第二个是利用闭包;
闭包的代码如下:
for (var i = 0; i < 5; i++) {
setTimeout((function(i) {
console.log(i);
})(i), 0)
}
正如文章刚开始所述,setTimeout回调中引用的是父层函数中变量i的值,在每次声明的时候,都会将当前的i值传递给闭包,其有自己单独的作用域,不再是引用同一变量,具体作用域情况如下所示:
以上仅为个人看法,如有错误,烦请指出,谢谢!