词法作用域是 JavaScript 作用域系统的一个基本方面,它决定了如何在嵌套的代码结构中解析变量名。本文档解释了词法作用域的工作原理、它在编译阶段是如何确定的,以及它在 JavaScript 编程中的重要性。有关基于词法作用域概念的作用域链的信息,请参阅 作用域链。有关利用词法作用域的闭包的信息,请参阅 闭包。
词法作用域(有时也称为静态作用域)是指变量的访问由源代码中变量和代码块的物理位置决定的作用域模型,而不是由运行时调用堆栈决定的。 “词法”一词指的是编译的词法分析(分词)阶段,即在执行代码之前对其结构进行分析。
在 JavaScript 中,词法作用域是变量解析的基础——它决定了在嵌套作用域中,一个引用指向哪个变量。
来源: scope-closures/ch1.md296-302 scope-closures/ch1.md1-9
JavaScript 使用一个两阶段的处理模型
词法作用域主要是在编译阶段确定的,而不是在代码执行阶段。这意味着,变量和函数在代码中的物理位置决定了它们的作用域,即使在代码运行之前也是如此。
在编译过程中,JS 引擎会创建一个所有词法作用域的映射,这些作用域定义了程序在执行期间需要什么。作用域规则由函数、代码块和变量声明相对于彼此的物理位置决定。
来源: scope-closures/ch1.md19-23 scope-closures/ch1.md47-51 scope-closures/ch1.md78-81 scope-closures/ch1.md303-309
理解词法作用域的一个有用比喻是彩色弹珠被分拣到匹配的彩色桶中
图示:嵌套词法作用域的范围气泡
使用我们的比喻,我们可以可视化示例代码中的三个不同的作用域“气泡”或“桶”
students、getStudentName 和 nextStudentstudentID 参数student 变量每个作用域都完全包含在其父作用域内。
来源: scope-closures/ch2.md12-26 scope-closures/ch2.md47-66 scope-closures/ch2.md76-78
当代码中引用(但未声明)一个变量时,JavaScript 引擎必须确定它属于哪个作用域
ReferenceError,或者(在非严格模式下)意外地创建一个全局变量图示:词法作用域解析过程
此查找过程的概念就像依次询问每个作用域管理器是否知道某个特定变量,从当前作用域开始向外移动,直到找到匹配项或到达全局作用域。
来源: scope-closures/ch2.md80-89 scope-closures/ch2.md224-237 scope-closures/ch3.md10-19
在分析词法作用域时,变量扮演两种角色之一
var students = [ /* ... */ ]; // "students" is a target
console.log(students); // "students" is a source
编译器在编译和执行阶段以不同的方式处理这些角色
来源: scope-closures/ch1.md197-202 scope-closures/ch1.md205-238 scope-closures/ch1.md249-256
遮蔽发生在当同一个变量名在不同的嵌套作用域中使用时。内部变量“遮蔽”了外部变量,使得外部变量在该内部作用域中无法访问。
图示:嵌套作用域中的遮蔽
当内部作用域中的变量与外部作用域中的变量同名时,它会创建一个新变量,而不是访问外部变量。在该内部作用域中对该变量名的任何引用都指向内部(遮蔽)变量,而不是外部(被遮蔽)变量。
来源: scope-closures/ch3.md50-93 scope-closures/ch3.md145-168
并非所有声明遮蔽的组合都是允许的
let 可以遮蔽 varvar 不能遮蔽 let禁止 var 遮蔽 let 的限制是,因为 var 声明是函数作用域的(不是块作用域),并且会尝试“跳过”或“跨越”块作用域的 let 声明的边界。
来源: scope-closures/ch3.md205-261
全局作用域是 JavaScript 程序中最外层的词法作用域。它充当
undefined、Array、Object 等)console、document 等)全局作用域是作用域链查找过程的最后一步。如果在任何嵌套的词法作用域中都找不到变量,JS 引擎将在判定变量未声明之前检查全局作用域。
来源: scope-closures/ch4.md10-11 scope-closures/ch4.md92-107
虽然词法作用域通常在编译时确定,但在运行时(仅在非严格模式下)有两种已弃用的修改作用域的方法。
eval() 函数:可以解析包含修改当前作用域的声明的代码。with 语句:可以将对象视为一个作用域。强烈不推荐使用这些技术,并且在严格模式下已禁用,因为它们:
来源: scope-closures/ch1.md264-294
词法作用域对几个重要的 JavaScript 概念至关重要:
let 和 const 利用块级别的词法作用域。图示:词法作用域作为 JavaScript 功能的基础
理解词法作用域对于掌握这些更高级的 JavaScript 概念和模式至关重要。
来源: scope-closures/ch8.md4-11 scope-closures/ch8.md387-390
要在代码中有效利用词法作用域:
eval() 或 with 在运行时修改作用域。遵循这些实践有助于创建更易于维护、更可预测的代码,并最大限度地减少与变量访问和可见性相关的问题排查。
来源: scope-closures/ch1.md290-294 scope-closures/ch2.md296-298 scope-closures/ch3.md125-126
词法作用域是在编译时根据代码中编写变量和作用域块的位置确定的。这种静态作用域模型使 JavaScript 引擎能够优化变量访问,并构成了闭包和模块等重要 JavaScript 模式的基础。
当你在 JavaScript 中引用变量时,引擎会使用编译期间创建的词法作用域映射来解析你正在访问的变量,沿着作用域链从当前作用域向外查找,直到找到匹配项。
理解词法作用域对于编写精确、可维护的 JavaScript 代码以及有效利用语言的高级功能至关重要。
来源: scope-closures/ch1.md296-309 scope-closures/ch3.md388-393