美文网首页
Javascript入门:闭包也不过就是这么回事儿嘛

Javascript入门:闭包也不过就是这么回事儿嘛

作者: 饥人谷_bobcyd | 来源:发表于2017-08-17 19:09 被阅读0次

    闭包


    1. 什么是闭包?

    作为一名前端无知小白,我猜MDN的中文版一定是机器翻译的,因为有时翻到上面的中文怎么看都不像是人话,比如闭包的定义:

    Closures (闭包)是使用被作用域封闭的变量,函数,闭包等执行的一个函数的作用域。通常我们用和其相应的函数来指代这些作用域。(可以访问独立数据的函数)

    能看得懂这个定义才真有鬼了。

    好的,我们还是用蹩脚的英文来看看吧:

    “A closure is the combination of a function and the lexical environment within which that function was declared.“

    闭包是函数以及在函数声明下的词法环境的结合。

    函数?变量?词法环境?

    似乎感觉懂了一点点?

    翻了下JS权威指南,里面说”Javascript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包“。

    所以我们从变量的作用域开始谈起。

    2. 变量作用域

    先来说几个概念:

    全局变量:在所有作用域都可访问的变量,在函数外定义的变量就是全局变量

    局部变量:在函数中使用关键字声明的变量,它的作用域只在声明该变量的函数内,在函数外面是访问不到该变量的。

    词法作用域:词法作用域也叫静态作用域,也就是说函数的作用域在函数定义的时候就决定了,而不是调用的时候决定。JavaScript采用静态作用域,变量的作用域完全由写代码期间函数声明的位置来定义的。

    话不多说,上代码:

    代码1:

    var func = function(){

         var a = 'closure'

           console.log(a);         // closure

    }

    func();

    console.log(a); // Uncaught ReferenceError: a is not defined

    局部变量a只能在函数内部使用,函数调用结束时,该变量就会被垃圾回收机制回收而销毁

    代码2:

    var value = 1;

    function foo() {

        console.log(value);

    }

    ​function bar() {

        var value = 2;

        foo();

    }

    bar(); 

    foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。

    3. 变量的生命周期

    我们知道的事情有:

    1. 一个局部变量当定义该变量的函数调用结束时,该变量就会被垃圾回收机制回收而销毁。再次调用该函数时又会重新定义了一个新变量。

    2. 按照代码书写时的样子,内部函数可以访问函数外面的变量。

    那么,如果在函数内部声明一个内部函数,并将内部函数作为值返回,调用外部函数之后,内部函数保持对外部函数词法作用域的引用,这样会发生什么呢?由于内部函数作为值返回了出去,所以外层函数执行完毕,其词法作用域中的变量也不会被销毁。

    这就是闭包!

    老规矩,上代码:

    function foo() {

        var a = 2;

        function bar() {

        console.log( a );

    }

       return bar;

    }

       var baz = foo();

     baz(); // 2 —— 朋友,这就是闭包的效果。

    这段代码非常清晰地展示了闭包,函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作一个值类型进行传递。在 foo() 执行后,其返回值赋值给变量 baz 并调用 baz(),也就是调用了内部函数bar()。bar()在自己定义的词法作用域以外的地方执行。由于bar()保持着队foo()词法作用域的引用,所以foo()在执行完后内部作用域也不会被程序销毁。

    循环和闭包

    for循环是常见的说明闭包的例子,也是我这样的小白非常容易错的地方。

    for (var i = 0; i < 5; i++) {

    setTimeout(function () {

    console.log(i)

    }, 0)

    }

    我们可能会简单的以为控制台会打印出‘ 0 1 2 3 4 ’,可事实却打印出了5个‘ 5 ’,这又是为什么呢?我们发现,setTimeout 函数时异步的,等到函数执行时,for循环已经结束了,此时的 i 的值为 5,所以 function() { console.log(i) } 去找变量 i,只能拿到 5。

    所以,改进一下:

    for (var i = 0; i < 5; i++) {

    !function (i) {

    setTimeout(function () {

    console.log(i)

    }, 0)}(i)

    我们套用了一个立即执行函数,当i=0, 此时 function(i){} 此匿名函数中的 i 的值为 0,等到 setTimeout 执行时顺着外层去找 i,这时就能拿到 0。如此循环,就能拿到想要的 0 1 2 3 4。

    作为小白,可能还是太明白什么是立即执行函数,下篇我们会详细介绍。这里我们先换个例子。

    function constfunc(v){return function(){  return v;  };} // 这个函数返回一个总是返回v的函数

    var funcs = []; // 创建一个空的数组

    for(var i=0; i<10;i++){funcs[i] = constfunc(i)}

    for(i=0;i<funcs.length;i++) {console.log(funcs[i]())}

    这样就可以打印出0~9了。

    不知道你明白了没有,反正我是明白啦~

    今天给自己加个鸡蛋!

    相关文章

      网友评论

          本文标题:Javascript入门:闭包也不过就是这么回事儿嘛

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