• Home
  • About
    • 知易行难 photo

      知易行难

      front-end developer

    • Learn More
    • Email
    • Github
    • Weibo
  • Posts
    • All Posts
    • All Tags
  • Projects

对闭包(Closure)的理解

08 Apr 2017

Reading time ~1 minute

最近在看一本书《你不知道的JavaScript(上)》,这本书里第5章:作用域闭包,是关于闭包的介绍,讲的特别好。看完这个介绍,我觉得有必要整理下我对闭包这个概念的理解,因为之前我对闭包的理解比较模糊。

下定义

闭包是什么?对这个概念下一个定义。

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即函数是在当前词法作用域之外执行。

这里有一个概念,词法作用域,这又是什么? 看下面代码

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

简单来说词法作用域就是,当前变量或者函数所处的位置所在的作用域。从上面的代码来看,bar函数的词法作用域就是foo函数内部这个作用域。

举例说明什么是闭包

例1:

function foo(){
    var a = 2;
    function bar(){
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz();  //2 ------这就是闭包的效果,从foo函数的外部可以访问它内部的变量2

说明:调用foo()函数会得到一个bar函数的引用,又把这个引用赋值给baz,当调用baz()时,其实是调用了bar(),从闭包的定义来看,bar函数是在它的当前词法作用域(foo函数包裹的作用域)之外执行了,所以产生了闭包。

例2:

function foo(){
    var a = 2;
    function baz(){
        console.log(a); //2
    }
    bar( baz );//把baz函数当作参数
}
function bar(fn){
    fn();   //这里产生了闭包
}

foo();//调用foo函数

说明:当调用foo函数时,foo里面又调用了bar函数,并且把baz函数作为参数传递给了bar,但是bar函数是在baz的词法作用域外面的,也就是说baz函数在它的词法作用域外面被调用了,从闭包的定义来看,这里产生了闭包。

例3:

var fn;
function foo(){
    var a = 2;
    function baz(){
        console.log(a);
    }
    fn = baz;   //将baz赋值给全局变量fn
}

function bar(){
    fn(); //这就是闭包
}

foo();
bar();  //2

上面这个例3也产生了闭包。无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

闭包的副作用

我们都知道JavaScript的内存管理方式是通过它自己的垃圾回收器来实现的。 用上面例1的代码来看,在foo函数执行后,通常会期待foo的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。但是由于有闭包的存在,可以阻止这件事的发生。事实上,内部作用域任然存在,因此没有被回收。谁在使用这个内部作用域呢?是bar()本身在使用。

for循环与闭包

下面是一道好玩的题目:每隔一秒打印一个数,数字是1-5 首先是我们会想到这么实现,代码如下

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

但是这种实现得出的结果是错的。这段代码输出的结果是:每隔一秒输出一个6,一共输出5个6

下面是第一种解决方法:

//ES5的解决方案
for (var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
    })(i)
}
//console.log('out:' + i)   //假如这句不注调的话,答案:先输出6,然后每隔一秒输出1,2,3,4,5

下面是第二种解决方法:

//ES6的解决方案,利用let
for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i)
    }, i * 1000)
}


闭包closure Share Tweet +1