JavaScript闭包

我们先看下面一个例子:

function fun(){
    var arr = [];
    for(var i = 0; i < 10; i++){
        arr[i] = function (){
            return i;
        }
    }
    return arr;
}
var fn = fun();
console.log(fn[0]()) //10
console.log(fn[1]()) //10

可能以为会打印0,1。但结果却都是10。

对这个问题我们可以用let定义i来解决

将 var  i   改为   let  i   打印出来的就是 0,1

但 let  有一些限制条件

比如  如果我们需要在for循环的外面 打印 i   -> console.log(i),使用let的话就会报错,提示 i is not defined.

因为 let 只在{}里面的部分有效。并且 let定义变量时需要在调用该变量的前面,并不会将变量声明提前。

也可以通过闭包来解决

首先我们先了解什么是闭包(通俗易懂的)

我们知道在js中 可以在子级访问父级中定义的变量,但不能在父级中访问子集的变量。

这是作用域与作用域链造成的。

下面我们来粗略了解一下作用域链

作用域链

作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量

当需要查找某个值时,从自身开始找,找不到就往父级找,依次往上找。都没有则报错

作用域链上有两个对象,第一个时定义函数参数和局部变量的对象,第二个是全局对象。如果在嵌套的函数中,作用域上的对象数更多。

基本方式是入栈排列。自身占第一个,后面是父级,再是父级的父级,依次,知道全局GO。

值得注意的是,对于嵌套函数来说,每次调用外部函数时,内部函数又会重新定义一遍。所以每次调用外部函数时,内部函数的代码都是相同的,他们的作用域链不同。如下例子:

function fun(){
    var num = 0;
    function jia(){
        num++;
        console.log(num);
    }
    function jian(){
        num--;
        console.log(num)
    }
    return [jia,jian];
}
var jia = fun()[0];
var jian = fun()[1];
jia()
jian()

打印结果是 1,-1.  而不是1,0.

那我们想在父级中访问子集的变量,像这种内部函数的作用域链仍然保持着对父级函数活动对象的引用,就是闭包。

通过上面的这个例子来说明:

将代码改变一下:

function fun(){
    var arr = [];
    for(var i = 0; i < 10; i++){
        (function(i){
            arr[i] = function (){
                return i;
            }
        })(i)    
    }
    return arr;
}
var fn = fun();
console.log(fn[0]()) //0
console.log(fn[1]()) //1            

这样使用闭包后就可以打印出 0,1 了

当内部的函数被保存到了外面,就会形成闭包。

但闭包也有自己的缺点:占内存。

占内存:闭包导致原有的函数执行完成了以后作用域链不会得到释放,会占据大量的内存。