作用域链是 JavaScript 中确定变量如何在嵌套作用域中查找的基本机制。它代表了嵌套在其他作用域内的作用域之间的连接,并定义了可以访问变量的路径。理解作用域链对于掌握 JavaScript 中的词法作用域至关重要,也是闭包等高级概念的重要基础(有关闭包的更多信息,请参阅 闭包)。
本文档将解释
有关全局作用域在作用域链中的作用,请参阅 全局作用域周围。
作用域链之所以存在,是因为 JavaScript 允许作用域相互嵌套。当作用域嵌套时,它们会形成一种定向关系,即内部作用域可以访问其外部包含作用域中的变量,反之则不然。
来源: scope-closures/ch3.md17-18 scope-closures/ch2.md66-69
每个作用域完全包含在其父作用域中——作用域绝不会部分存在于两个不同的外部作用域中。这种嵌套创建了一个链状结构(作用域链),该结构决定了变量查找规则。
当内部作用域中的代码引用变量时,JavaScript 引擎首先检查当前作用域。如果在当前作用域中找不到变量,它会检查下一个外部作用域,一直向外查找,直到
ReferenceError来源: scope-closures/ch3.md19-27 scope-closures/ch2.md233-237
虽然作用域链查找通常被解释为一种运行时过程(检查每个作用域直到找到变量),但这种概念模型与现代 JavaScript 引擎的实际实现不同。
查找行为可以概念化如下:
实际上,变量作用域信息主要在编译期间确定。
有些情况仍然需要运行时查找。
来源: scope-closures/ch3.md20-48 scope-closures/ch2.md85-92
一个有助于可视化作用域链的方法是使用“弹珠和桶”的比喻。可以想象
来源: scope-closures/ch2.md12-21 scope-closures/ch3.md10-16
在这个比喻中,变量查找就像通过当前桶寻找弹珠。如果不在当前桶中,则检查下一个外部桶,依此类推,直到找到弹珠或检查完所有桶。
作用域链最重要的方面之一是它如何处理多个作用域包含同名变量的情况。
当内部作用域中的变量与外部作用域中的变量同名时,就会发生变量遮蔽。发生这种情况时,内部变量会“遮蔽”外部变量,使得该内部作用域无法访问外部变量。
来源: scope-closures/ch3.md50-88
当函数中的参数或变量与外部作用域中的变量同名时,函数内部对该名称的引用将始终指向内部变量,而不是外部变量。
在此示例中,由于变量遮蔽,函数内部对 studentName 的引用始终指向参数,而不是全局变量。
来源: scope-closures/ch3.md60-77
虽然被遮蔽的变量通常是不可访问的,但有一个特殊的技巧可以通过全局对象来访问已被遮蔽的全局变量。
此技巧仅适用于
var 或 function 声明的变量window 指向全局对象)来源: scope-closures/ch3.md96-122
并非所有变量遮蔽的组合在 JavaScript 中都是允许的。具体来说:
let 可以遮蔽 varvar 不能遮蔽 let(跨越块边界)发生这种情况是因为 var 声明是函数作用域的,并且会“跨越”块作用域的 let 声明的边界,而 JavaScript 不允许这样做。
来源: scope-closures/ch3.md205-261
函数声明将其名称标识符添加到其封闭作用域。但是,函数表达式在其名称方面表现不同。
使用具名函数表达式时,函数名称仅在函数自身作用域内可用。
在此示例中,askQuestion 被添加到外部作用域,但 ofTheTeacher 仅在函数内部可访问。
来源: scope-closures/ch3.md264-310
匿名函数表达式不创建任何额外的名称标识符。
来源: scope-closures/ch3.md329-336
ES6 箭头函数遵循与常规函数相同的词法作用域规则。尽管存在普遍的误解,但它们在作用域链方面的行为并无不同。
箭头函数始终是匿名的(没有直接相关的标识符),但它们仍然像常规函数一样创建独立的嵌套作用域。
来源: scope-closures/ch3.md342-386
让我们通过一个包含多个嵌套作用域的完整示例来可视化作用域链。
来源: scope-closures/ch2.md15-70 scope-closures/ch3.md10-16
该图展示了变量查找如何遍历作用域链。当在 for 循环中引用 students 时,查找从函数作用域开始,在该作用域中未找到,然后继续到定义它的全局作用域。同样,studentID 在函数作用域中找到,而 student 在循环的块作用域中找到。
作用域链是 JavaScript 中决定嵌套作用域如何访问变量的关键机制。理解作用域链的规则对于编写可维护的 JavaScript 代码并避免与变量作用域相关的常见错误至关重要。
要记住的关键点
作用域链构成了闭包和模块模式等其他高级 JavaScript 概念的基础。