美文网首页
JavaScript 闭包学习笔记

JavaScript 闭包学习笔记

作者: ltaoo | 来源:发表于2017-02-21 19:59 被阅读7次

    什么是闭包?直接上代码:

    var name = "global"
    function wrapper() {
      var name = "local"
      function echo() {
        4、-------------------------
        console.log(name)
      }
      // 2、-------------------------
      return echo
    }
    // 1、---------------------------
    var foo = wrapper()
    // 3、---------------------------
    foo() // 打印 local,这就是闭包
    

    详细解读

    之前在学习闭包时,简单记忆为:

    函数能够 记住 定义时作用域中的变量。

    比如这里的name="local",但是一直没有深入了解为什么能够访问。在了解了作用域链后,答案很明显,因为函数对象的[[scope]]属性。

    像之前一样对作用域链的变化进行详细分析。

    1、调用 wrapper() 前的作用域链:

    // 调用 wrapper() 前的作用域链
    scopes = {
      0: {
        name: "global",
        wrapper: {
          name: "wrapper",
          [[scope]]: {
            0: {...}
          }
        },
        foo: undefined,
        // 以及全局变量与方法
      }
    }
    

    2、调用wrapper函数时的作用域链,将warpper的变量对象加入到自身的[[scope]]对应的socpes中,就是这样的:

    // 调用 wrapper 函数时
    scopes = {
      1: {
        name: "local",
        echo: {
          name: "echo",
          [[scope]]: {
            1: {...},
            0: {...}
          }
        }
      },
      0: {
        name: "global",
        wrapper: {
          name: "wrapper",
          [[scope]]: {
            0: {...}
          }
        },
        foo: undefined,
        // 以及全局变量与方法
      }
    }
    

    重点在于echo对象的[[scope]]保存了两个变量对象,然后将这个echo对象返回。

    3、调用foo函数前的作用域链:

    scopes = {
      0: {
        name: "global",
        wrapper: {
          name: "wrapper",
          [[scope]]: {
            0: {...}
          }
        },
        foo: {
          name: "foo",
          [[scope]]: {
            1: {...},
            0: {...}
          }
        },
        // 以及全局变量与方法
      }
    }
    

    这里和 1、---- 处不同在于fooundefined变成了对象,并且这个对象就是3、----处的echo对象。

    4、调用 foo 函数时的作用域链:

    scopes = {
      2: {
        this: Window
      },
      1: {
        name: "local",
        echo: {
          name: "echo",
          [[scope]]: {
            1: {...},
            0: {...}
          }
        }
      },
      0: {
        name: "global",
        wrapper: {
          name: "wrapper",
          [[scope]]: {
            0: {...}
          }
        },
        foo: {
          name: "foo",
          [[scope]]: {
            1: {...},
            0: {...}
          }
        },
        // 以及全局变量与方法
      }
    }
    

    echo函数内会寻找name变量,在作用域链的第一个变量对象中没有找到(2对应的对象)就往下继续找,在1中找到了,并且是local,所以最终打印出local

    同样以一张图片来更为直观的了解:

    作用域链变化
    • 红色箭头指 2 处的作用域链来自 wrapper 的 [[scope]] 属性
    • 橘色箭头指 echo 函数的 [[scope]] 属性等于 2 处加入 wrapper 变量对象后的作用域链
    • 蓝色箭头指 3 处作用域链中全局变量对象 0 中的 foo 其实就是 echo(因为 wrapper 返回 echo 赋给 foo)
    • 黑色箭头指 4 处的作用域链来自 foo(也就是 echo )的 [[scope]] 属性,而这个属性又指向 2 处的作用域链,所以是有两个变量对象

    echo函数内的console.log(name)查找name时首先在自身的变量对象中寻找,没有找到后往下继续寻找,找到了local后就返回,所以不会再继续往下找到global。最终打印local

    随处都是闭包

    当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
    —— 《你不知道的 JavaScript 》(上卷)

    按照上面的定义,只要声明了函数,函数是能够记住当时的作用域链的,所以只要声明了函数,就产生了闭包?

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

    bar记住了定义时的作用域链,在这作用域链顶端的变量对象有a: 2的键值对,在其他任何地方调用bar都能够访问到a变量。

    但是更严格来说,函数在当前词法作用域之外执行,才算闭包。

    参考

    相关文章

      网友评论

          本文标题:JavaScript 闭包学习笔记

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