学习来源:https://www.zhihu.com/people/wang-wang-69-90-11/answers
首先清楚一点,在作用域篇有详细讲。
作用域链的第一个对象:引用本函数的参数和局部变量
作用域链的第二个对象:引用外层函数的参数和局部变量
.....
作用域链的最后一个对象:引用的全局的执行环境对象,也就是window对象
闭包:
闭包简单的说就是一个函数能访问外部函数的变量,这就是闭包,比如说:
function a(x){
var tem=3;
function b(y){
console.log(x+y+(++tem));
}
}
a函数中的b函数就是闭包了,b函数可以使用a函数的局部变量,参数。
最典型的闭包应该是下面这样,将定义在函数中的函数作为返回值:
function a(x){var tem=3;
function b(y){
console.log(x+y+(++tem));
}
return b;
}
a(2)(3);
或是作为参数被传递
function a(){
var x= 2;
function b(){
console.log(x); //2
}
foo(b);
}
function foo(fn){
fn();
}
for(var i=0;i<2;i++){
setTimeout(function(){
console.log(i);
},0);
}
上面这段代码的执行结果是2,2而不是0,1,因为等for循环出来后,执行setTimeout中的函数时,i的值已经变成了2,这就是没有隔离作用域所造成的。
在循环的过程中,并没有把函数的返回值赋值给数组元素,而仅仅是把函数赋值给了数组元素。这就使得在调用匿名函数时,通过作用域找到的执行环境中储存的变量的值已经不是循环时的瞬时索引值,而是循环执行完毕之后的索引值。
由此,可以利用IIFE传参(立即执行函数:在定义函数之后,立即调用该函数)和闭包来创建多个执行环境来保存循环时各个状态的索引值。因为函数传参是按值传递的,不同参数的函数被调用时,会创建不同的执行环境
for(var i=0;i<2;i++){
(function(i){
setTimeout(function(){
console.log(i);
},0)
})(i);
}
这样就会输出0,1,我们的立即执行函数创建了一个作用域,隔离了外界的作用域。
只要将内部函数传递到所在的作用域以外,它都会持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包
严格来说,闭包需要满足三个条件:【1】访问所在作用域;【2】函数嵌套(访问父级作用域);【3】在所在作用域外被调用(作为返回值,作为参数)
闭包的缺点是因为内部闭包函数可以访问外部函数的变量,所以外部函数的变量不能被释放,如果闭包嵌套过多,会导致内存占用大,要合理使用闭包。