自 ES6 更新以来,JavaScript 具有三种类型的作用域:
- 全局作用域 Global scope
- 函数作用域 Function scope
- 块级作用域 Block scope
从执行上下文的角度来看,作用域是什么?
全局作用域就是全局执行上下文,函数作用域与函数执行上下文相关。
ES6 中引入的块作用域与其两个作用域不同。
全局作用域 Global scope
了解块作用域的最简单方法是与其他两个作用域比较。
变量在全局作用域和函数作用域中的工作方式类似,因此我在本文中仅讨论全局作用域和块作用域。
在这种情况下,我们只有一个全局执行上下文和一个全局变量环境。
第二个 apple 分配会覆盖前一个。执行结束时,只有一个 apple 变量保存值“banana”。
从作用域的角度来看,我们可以说 apple 变量处于全局作用域中。
块作用域 Block scope
我们可以用 let 重写上面的例子来引入一个新的作用域,即块作用域。
控制台打印两个不同的值。第一个 apple 变量的值为“apple”,而 if 块中的变量的值为“banana”。
为什么我们有两个同名的变量?
词法环境 Lexical Environment
让我们通过 two-step process ( 编译,执行 ) 揭开其工作原理的神秘面纱。
在编译步骤中,未定义的 apple 变量被添加到全局执行上下文中。
此时,JavaScript 引擎决定跳过第二个 apple ,原因有两个:
- 它是用 let 创建的变量
- 它位于块作用域内。
接下来,执行步骤开始。第一个 apple 变量被赋予值“apple”。
当读取 if 块代码时,会发生嵌套编译步骤。创建第二个未定义的 apple 变量。
这个 apple 变量不是在变量环境中创建的,而是添加到词法环境中的。
很快,词法环境中的 apple 就被赋予了“banana”这个值。
现在我们有两个同名的变量在两个环境中管理。这就是 JavaScript 引擎处理 let 的方式,并且仍然向后兼容 var。
词法环境中的作用域栈
为了更好地理解 let 和 var 之间的区别,让我们将它们组合在一起,形成一个有趣的示例。
在编译步骤中,未定义的 apple 和 grape 变量在变量环境中初始化。 grape 初始化被提升。同时,在词法环境中创建了一个 banana 变量。
执行开始。 apple 被分配了一个值“全局 apple ”,而 banana 则被分配了“全局 banana ”。
是时候处理块中的变量了。
嘿,这是另一个 banana 变量。我们可以在同一个词法环境中拥有两个 banana 变量吗?
当在块作用域中看到 let 和 const 变量时,JavaScript 会为它们创建一个单独的区域。
词法环境为其变量维护一个类似栈的结构,因此具有相同名称的变量不会相互冲突。
在这里,未定义的 Banana 和 Orange 变量位于独立作用域中。
很快,两者都被赋予了相应的值。
执行完最后一次赋值后,所有变量都准备好了。
当打印第一个变量时,JavaScript 引擎首先尝试在词法环境中从上到下查找 apple。然后它检查全局变量环境并找到 apple ,注销“全局 apple ”。
当搜索 banana 时,JavaScript 引擎遵循相同的步骤,找到它,并打印“阻止 banana ”。
此时,块中已经没有更多的可执行代码了。块作用域被删除。
脚本继续执行。它在全局变量环境中找到 banana 和 grape ,并相应地注销“全局 banana ”和“全局 grape ”。
当脚本搜索 orange 时,该变量在任何地方都不存在,因为 orange 存在的作用域已被删除。它会抛出错误“ orange 未定义”。
整个过程结束。
除了作用域之外,还有其他与 let 和 const 相关的问题吗?
创建、初始化和赋值的技巧
一个变量从编译到执行,要经历三个步骤:
- 创建
- 初始化
- 任务
控制台会打印什么?是 apple 吗?或者,是 banana 吗?
令人惊讶的是,错误显示“初始化之前无法访问 apple ”。
该错误与变量提升有关。
- 对于 let 变量,它的创建会被提升,但初始化和赋值不会。
- 对于 var 变量,它的创建和初始化会被提升,但赋值不会。
- 对于一个函数来说,它的创建、初始化和赋值是同时提升的。
人们将变量初始化之前的代码区域命名为临时死区 ( temporal dead zone )。
- 如果您在创建之前尝试访问变量,您会看到错误“[变量名称]未定义”。
- 如果您决定在初始化之前访问变量,您会看到错误“无法在初始化之前访问[变量名称]”。
- 如果在赋值之前打印变量,您会看到值未定义。
让我们了解有关作用域和词法环境的所有信息。
要点
- 词法环境是执行环境的另一个组件。
- 块作用域中的 let 和 const 变量是在执行步骤而不是编译时创建的。
- 这些变量存储在词法环境中。
- 多个块作用域在词法环境中被维护为栈结构。
- 当 JavaScript 引擎执行块作用域中的所有代码时,相关的 let 和 const 变量将被删除。