美文网首页程序员
《你不知道的Javascript(上)》阅读笔记(二)

《你不知道的Javascript(上)》阅读笔记(二)

作者: Bless_L | 来源:发表于2016-12-20 09:45 被阅读284次

    我又来了,隔了这么久才写第二篇,拖延症的我~~~

    闭包

    定义

    闭包是个老生常谈的话题了,网上也有一大堆相关的文章,不过既然是笔记,那也简单提一下吧。
    先看wiki中闭包的定义:

    闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

    看定义,有两个重点,自由变量和函数。那什么是自由变量?
    先看个最简单的闭包例子:

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

    想必你也在各种文章看到过类似的例子,那我们就把它往定义中套一套。
    我在上篇阅读笔记中说过,内层作用域可以访问到外层作用域的变量。没错,这个bar访问到的外部变量a,相对于bar来说就是一个自由变量。这其实也就满足了最广义的闭包定义了,但我们js中通俗意义上的闭包是还要满足后面这句

    这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

    离开了创造它的环境。是的,经过foo那条语句的执行,bar已经被返回并赋值到baz中,也就是暴露在了全局作用域中,离开了创造它的环境。
    这个被引用的自由变量将和这个函数一同存在。也就是说,变量a(连同整个内部作用域)并不会因为foo被执行完了就消失,而是会保留在内存中。
    就像下面的例子

    function foo(){
        var a = 2;
        return function bar(){
            console.log(a++)
        };
    }
    var baz = foo();
    baz();//2
    baz();//3
    baz();//4
    baz();//5
    

    bar中执行了a++,你会看到,每次运行baza的值是累加的,而不是2

    综上,这个baz就是我们通常所说的闭包了。

    常见闭包

    一个很常见的例子就是在for循环里使用闭包
    如下面

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

    你可会以为这段代码是以一秒的间隔输出1~5
    但实际上,这段代码在运行时会以每秒一次的频率输出五次6
    6是怎么来的?在循环结束后,i的值为6。也就是说,timer里面的打印i的在循环结束后的i
    仔细想想也的确如此,setTimeout的回调是在循环结束后才调用的,我们期望每次循环中会有一个i的副本被保存timer中,以至于在后面输出,但事实上for循环并没有提供这样的机制,所以每次输出的i都是在循环结束后的值6i是存在于全局作用域中被共享的。

    这个时候可以用闭包解决

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

    或者更常见的写法是这样

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

    我们用一个立即执行的匿名函数来构造一个封闭的内部作用域,复制一个i的副本,与timer一起构成一个闭包,从而达到每次保存i的值的目的。
    这便是闭包的常见用法。

    PS:ES6中,我们可以直接用let来代替var,生成块级作用域,达到同样的效果而不用闭包。

    总结

    无论你懂不懂闭包,我想你的代码中已经有意无意地存在了闭包。
    闭包作用有好多,变量封装,私有化;函数嵌套;函数可以很方便地访问到外部变量等。但不合理的应用也会很容易造成内存泄漏,代码混乱等问题。
    重点是要理解闭包及其背后的作用域机制,这样才能更好用闭包来为我们服务。

    相关文章

      网友评论

        本文标题:《你不知道的Javascript(上)》阅读笔记(二)

        本文链接:https://www.haomeiwen.com/subject/kmyxvttx.html