1

什么是闭包?

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的
下面用一些代码来解释这个定义:

function foo() {
    var a = 2;
    
    function bar() {
        console.log(a);   // 2
    }
    
    bar();
}

foo();

这是闭包吗?
技术上来讲,也许是。但根据前面的定义,确切地说并不是。最准确地来解释 bar() 对 a 的应用方法是词法作用域的查找规则(即在 bar() 的函数作用域中无法找到 a,则向上一级所嵌套的 foo() 的作用域中查找),而这些规则只是闭包的一部分。
下面再看一段代码,清晰地展示了闭包:

function foo() {
    var a =  2;
     
    function bar() {
        console.log(a);
    }
    
    return bar;
}

var baz = foo();

baz();   // 2   这就是闭包的效果

函数 bar() 的词法作用域能够访问 foo() 的内部作用域,然后将 bar() 函数本身作为一个值类型进行传递。在这段代码中,我们将 bar 所引用的函数对象本身作为返回值。在 foo() 执行后,其返回值赋值给变量 baz 并调用 baz(),实际上只是通过不同的标识符引用调用了内部函数 bar()
在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁。而闭包的神奇之处可以阻止作用域被销毁,被回收。那么是谁再使用这个内部作用域?是 bar() 本身在使用。bar() 拥有覆盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。这个引用就叫做闭包。
再据两个例子:

function foo() {
    var a = 2;
    
    function baz() {
        console.log(a);   //2
    }
    
    bar(baz);
}

function bar(fn) {
    fn();   // 这就是闭包
}
function wait(message) {
    
    setTimeout(function timer(){
        console.log(message);
    },1000);
}

wait("Hello World");

循环和闭包

先看下面的例子:

for(var i=1; i<=5; i++){
    setTimeout(function timer() {
        console.log(i);
    },i*1000);
}

这段代码在运行时会以每秒一次的频率输出五次6.为什么会这样呢?
首先解释6是怎么来的。这个循环的终止条件是 i<=5。条件首次成立时 i 的值是6.因此,输出显示的是循环结束时 i 的最终值。
延迟函数的回调会在循环结束时才执行,当定时器运行时即使每个迭代中执行的是 setTimeout(..,0),所有的回调函数依然是在勋魂结束后才会执行,因此每次都输出6.根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都是被封闭在一个共享的全局作用域中,因此实际上只有一个 i
再看下一个代码,给上一代码加入更多的词法作用域,且要加入实质内容才能起作用。

for(var i=1; i<=5; i++){
    (function() {
        var j = i;
        setTimeout(function timer(){
            console.log(j);
        },j*1000)
    })();
}

现在就能正常分别输出数字1~5,每秒一次,每次一个。


puhongru
581 声望58 粉丝

立志成为一名合格的前端开发工程师