闭包概念集合

一、创建闭包

创建闭包的常见方式,就是在一个函数内部创建另一个函数。

二、作用域链

当某个函数被调用的时候,会创建一个执行环境和相应的作用域链,然后使用arguments初始化对象。这个对象叫做活动对象。

在作用域链中,外部函数的活动对象始终处于第二位。以此类推,直到作用域链终点——全局执行环境。 首先,让我先来看看什么叫做活动对象。

function compare(a,b){
    if(a>b){
        return true;
    }else if(a<b){
        return false;
    }else{
        return 0;
    }
}
var result=compare(5,10);//false
console.log(result);

这段代码中,首先定义了compare()函数,然后在全局作用域中调用了它。在调用这个函数的时候,创建了arguments、a、b这三个活动对象。他们处在作用域链的第一位。

而result、compare被称为变量对象,他们处于全局执行环境下,在作用域链中处于第二位。

全局环境的变量对象始终存在,在函数中访问一个变量时,就会在作用域链中寻找具有相应名字的变量。函数执行完毕,活动对象就被销毁。而闭包的特殊就在于此,活动对象没有被销毁!

看这段代码:

function compare(propertyValue){
    return function(obj1,obj2){
        var a=obj1;
        var b=obj2;
        if(a<b){
            return true;
        }else if(a>b){
            return false;
        }else{
            return 0;
        }
    }
}
var fun=compare("value");
console.log(typeof fun);//function
var result=fun(5,10);
console.log(result);//true

函数compare()中包含了一个匿名函数,那么,该匿名函数,也就是闭包的作用域链中,就会有compare()函数的活动对象。因此,该闭包作用域链其实有三节:

第一节,闭包的活动对象,obj1,obj2 和arguments;

第二节,compare()函数的活动对象,arguments和propertyValue;

第三节,全局变量对象,compare。

这意味着什么意思呢?就是在compare()这个函数在执行完毕以后,他的活动对象propertyValue和arguments也不会被销毁。因为匿名函数的作用域链仍然在引用这个活动对象。也就是说,compare()执行完以后,其执行环境的作用域链会被销毁,但是他的活动对象却不会被销毁。除非匿名函数被销毁。

而在js中,内置的工具函数setTimeout,往往就会造就一个闭包。

function wait(message){
    setTimeout(function timer(){
        console.log(message);
    },1000);
}
wait('5');//5

timer()函数就是一个闭包,即使wait()执行1000毫秒后,内部作用域仍然不会消失。timer具有wait()作用域的闭包。

总结:由于闭包会携带外部函数的作用域,所以它会占用更多的内存。建议不要过多使用闭包,会导致内存占用过多。

三、闭包与变量

function fun(){
    var result=new Array();

    for(var i=0;i<4;i++){
        result[i]=function(){
            return i;
        };
    }
    return result;
}
var a=fun();
console.log(a);//Array(4) [, , , ]

 好吧,这段代码我还是看的半懂不懂。似乎理解他为什么会返回这个,似乎又不理解。而且和书上写的也不一样啊。也没返回4个“3”啊!这是为啥内?

for(var i=0;i<2;i++){
    setTimeout(function timer(){
        console.log(i);
    },i*1000);
}
console.log('daoda')//daoda;2;2

看到没有,竟然先输出了"daoda",然后又输出了两个“2”!

首先解释“2”是怎么来的。首先,这个循环终止的条件是i不再小于2,也就是说,条件首次成立时,i的值为2。因此,输出显示的是循环结束时i的值。

延迟函数的回调是在循环结束的时候才执行。即使执行的是setTimeout(...,0),所有的回调函数依然是在循环结束后才会执行。因此每次输出的都是2!

你以为每次迭代都会有对应的i,但是根据作用域的原理,尽管循环中的5个函数是在各个迭代部分中分别定义的,但是它们却都被封闭在一个共享的全局作用域中,因此实际上只有1个i。

for(var i=0;i<2;i++){
    (function(){
        setTimeout(function timer(){
            console.log(i);
        },i*1000);
    })();
}
console.log('蓝色橙汁');//"蓝色橙汁";2;2

IIFE会立即执行函数,为什么输出的还是两个“2”呢?

这是因为IIFE的作用域是空的。他要有自己的变量,用来在每次迭代中存储i的值才行。

代码写成这样才可以:

for(var i=1;i<=5;i++){
    (function(){
        var j=i;
        setTimeout(function timer(){
            console.log(j);//1,2,3,4,5!这样就可以了
        },j*1000);
    })();
}

上面这段代码可以做一些改进:

for(var i=1;i<=5;i++){
    (function(j){        
        setTimeout(function timer(){
            console.log(j);//1,2,3,4,5!这样就可以了
        },j*1000);
    })(i);
}

奇怪的是,为什么这里我就理解了?