美文网首页
作用域 和 闭包

作用域 和 闭包

作者: _1633_ | 来源:发表于2020-06-10 22:47 被阅读0次

变量提升带来的问题

    1. 后面定义的变量 会覆盖 前面的同名变量

        因为变量提升, 后定义的变量 会在不察觉的情况下 覆盖前面的 同名变量,导致 bug 而自己 没有察觉,尤其是在 非常长的代码块中。

    2. 应该销毁的变量没有被销毁

1

    for 循环中的 i , 结束后应该被销毁,结果却变成了全局变量, 在全局执行上下文中,污染变量。


ES6 带来的解决方案

    块级作用域: ES6 引入了 let 和 const 关键字,使 JS 拥有了块级作用域。

    块级作用域 就是使用⼀对大括号包裹的⼀段代码,比如函数、判断语句、循环语句,甚至单独的⼀个{ } 都可以被看作是 ⼀个块级作用域。

块级作用域

作用域

    作⽤域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是 变量 函数 的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

    在 ES6 之前, JS 只有两种作用域:全局作用域函数作用域

    全局作用域中的 对象 在代码的 任何位置 都能访问到,它的声明周期 与 页面的声明周期相同。

    函数作用域 就是定义在 函数内部 的 变量 或者函数,并且定义的变量或者函数只能在函数内部访问到函数执行结束后,函数内部定义的变量会被销毁。


块级作用域的实现

示例代码

分析代码执行过程:

    1 编译阶段,将 foo 放入全局执行上下文 变量环境中; 执行阶段,执行 foo:编译阶段:创建 foo 函数执行上下文,将用 var 声明的变量 a 和 c 放入变量环境 并赋值 undefined; 将用 let 声明的 b f放入词法环境 并赋值 undefined,此时, 函数内部的 块级作用域 中的 用 let 声明的变量 却 没有放入词法环境。

调用栈

        2. 执行到函数内的块级作用域时, 此时 a 已经赋值为 1, b = 2; 将块级作用域中 用 let 声明的变量, 单独 放在词法环境的另一个区域,它与另外一个区域 相互独立,且不影响作用域外面的变量。

        这个结构 与我们的调用栈一样,它也是一个栈结构,存放着用 let 和 const 声明的变量。进入一个块级作用域,就会将这些变量 压入栈中,当这个块级作用域执行完,就会弹出栈,这就是词法环境的结构。

调用栈

    3 执行 块级作用域中的 console.log,,在 词法环境 和 变量环境 中查找变量 a ,顺序为:首先在词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JS 引擎,如果没有查找到,那么继续在 变量环境 中查找。当执行完 块级作用域中的代码,这些变量就会从词法环境的栈顶弹出。 

出栈

    总结:块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,两者结合,JS 引擎也就同时 支持了变量提升 和 块级作用域了。


作用域链

    上面讲到在 词法环境 和 变量环境 找寻变量,这就 涉及到了作用域的另外一个概念,作用域链。

    首先看一段代码

作用域链

       如果按照上面的 模拟,应该是 1 ,那么这里为什么是 2 呢?

       在每个执行上下文的变量环境中,都包含了⼀个 外部引用,用了来指向外部的执行上下文,我们把这个外部引用称为 outer

outer

        bar 函数 和 foo 函数的 outer 都指向 全局执行上下文,所以,如果 函数 内部使用了外部变量,那么 JS 引擎都会去 全局执行上下文中查找。 我们把这个查找的链条叫做 作用域链。

        那么 为什么 bar 调用 foo 函数,foo 函数引用的是 全局的执行上下文, 而不是 bar 函数中的上下文呢,因为 词法作用域

        词法作用域 就是定义在词法阶段的作用域。简单来理解,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的(由代码中函数声明的位置来决定的, 跟在哪里调用这个函数没关系),词法作用域是静态的作用域。

        所以 在定义 foo 函数 和 bar 函数的时候,已经决定了 它们的上级作用域是 全局作用域,词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系 。


闭包

    首先看一段代码

输出结果 2

    我们模拟下 它执行时候的 调用栈,当执行到 return bar 这行的时候

调用栈

        foo 函数中定义的 bar 函数,根据 词法作用域,bar 函数中的 a 变量 是指向 foo 函数中的 a。当 return 的 bar 作为 baz 的 返回值的时候,函数 foo 已经执行完毕,但是 bar 函数 依然能使用 foo 函数中的 a 变量。

调用栈

        foo 函数执行完毕, foo 函数执行上下文弹出调用栈, 但是 返回值 bar 内部依然使用了 foo 函数中的变量 a, 所以 a 变量 保存在 内存中。除了 bar 函数 ,其他地方无法访问到 这个内存。

        在 JS 中,根据词法作用域的规则,内部函数 总是可以访问其外部函数中声明的变量,当通过调用⼀个外部函数返回⼀个内部函数后,即使该外部函数已 经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。 比如外部函数是foo,那么这些变量的集合就称为 foo 函数的闭包 。

        那么 闭包如何使用

调用栈

    查找 a 变量顺序:bar 函数执行上下文‒> foo函数闭包 ‒> 全局执行上下文, 也就是 内部函数一层层向外寻找,形成了一个作用域链。


   闭包的回收

        闭包如果使用不正确,很容易造成内存泄漏。

        1. 如果引⽤闭包的函数是⼀个全局变量,那么闭包会⼀直存在直到页⾯关闭;但如果这个闭包以后不再 使用的话,就会造成内存泄漏。

        2. 如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JS 引擎执行垃圾回收时,判断闭包内容,如果已经不再被使用了,那么 JS 引擎的垃圾回收器就会回收这块内存。

        所以,如果该闭包会⼀直使用,那么它可以作为全局变量而存在;但如果使用频率不高,并且内存占用较大,那就尽量让它成为⼀个局部变量。

相关文章

  • 作用域和闭包

    目录 概述 作用域编译过程词法作用域全局作用域函数作用域 闭包循环和闭包闭包的用途性能 总结 概述 作用域和闭包一...

  • javaScript门道之闭包

    闭包的学习路径:变量的作用域 -> 闭包的概念 ->闭包的应用 1.变量的作用域 变量的作用域分为作用于全局和作用...

  • 2023-01-12

    变量提升调用栈块级作用域作用域链和闭包 闭包 => 作用域链(词法作用域) => 调用栈(栈溢出) => 上下文...

  • 闭包(closure)

    ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...

  • 2018-01-07 关于javascript闭包和作用域的理解

    关于 javascript 闭包的一些思考 作用域 词法作用域 函数作用域 块作用域 闭包 什么是作用域? 作用域...

  • js作用域、闭包

    闭包 闭包作用 全局 局部 作用域链

  • 浓缩解读《JavaScript设计模式与开发实践》③

    三、闭包和高阶函数 3.1 闭包 3.1.1 变量的作用域 所谓变量的作用域,就是变量的有效范围。通过作用域的划分...

  • js闭包的理解

    什么是闭包 通俗的来讲,个人觉得闭包就是延长变量作用域的函数。众所周知js的作用域分为全局作用域和链式作用域。在函...

  • 14.JS基础之作用域与闭包

    作用域: 全局作用域 函数作用域 块级作用域(ES6新增) 常见的闭包有:作为函数返回值的闭包与作为函数参数的闭包...

  • 执行环境 & 作用域 & 闭包

    执行环境 & 作用域 & 闭包 执行环境 , 作用域 , 闭包 , 闭包应用 执行环境 执行环境定义了 当前环境的...

网友评论

      本文标题:作用域 和 闭包

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