作用域
几乎每个编程语言中都有变量的概念,变量可以存储值,也能被修改值。这种存储和访问变量的值的能力将状态带给了程序。
但是这些变量存储在哪里,最重要的是程序如何找到它?这些问题说明需要设计一套规则来存储变量,并且之后可以方便的找到变量,这套规则被称为作用域。
理解作用域
- 引擎 (如Chrome的V8引擎) 从头到尾负责整个JavaScript程序的编译及执行过程。
- 编译器 引擎的好朋友之一,负责语法分析及代码生成等脏活累活。
- 作用域 引擎的另外一位好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
var a = 2
,变量的赋值操作会执行两个动作,var a
首先编译器会在当前作用域中中声明一个变量(如果该变量在之前未声明过),a = 2
然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。
LHS和RHS
引擎在执行代码时会对变量进行LHS或RHS查询。
LHS查询:试图找到变量容器本身,从而可以对其赋值。 RHS查询:与简答地找到某个变量的值别无二致,也可以将其理解为retrieve his source value (取到它的源值),这意味着“得到某某的值”。
function foo(a) {
var b = a + 2
return a + b
}
var c = foo(2)
以上代码有3处LHS查询和4处RHS查询。
LHS
- var c = foo(2) 引擎:作用域你见过c吗,我要给他赋值。 作用域:见过,编译器刚刚声明了它。
- foo(2) -> a = 2 引擎:作用域你见过a吗 作用域:见过,编译器把他声明成foo的一个形参
- var b = a 作用域你见过b吗 引擎:作用域你见过b吗 作用域:见过,编译器声明过它。
RHS
- ... = foo(2)
- ... = a + 2
- return a + ...
- return ... + b
作用域嵌套
实际情况中,通常需要同时顾及几个作用域。
当一个块或函数嵌套在另外一个块或函数中时,就发生了作用域的嵌套。因此,当引擎无法在当前作用域中找到变量时,引擎就会在外层嵌套的作用域中寻找变量,直到找到该变量或者抵达最外层的作用域(也就是全局作用域)为止。
考虑如下代码
function foo(a) {
console.log(a + b)
}
var b = 2
foo(2) // 4
对b进行RHS查询时,引擎会先在函数foo所在的作用域中查询,如果无法查询,就会在当前作用域的外层嵌套作用域(在这个例子中是全局作用域)中查询。
异常
区分LHS和RHS是一件重要的事情,因为在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,这两种查询的行为是不同的。
考虑如下代码
function foo(a) {
console.log(a + b)
}
foo(2) // ReferenceError: b is not defined
如果RHS查询在所有嵌套的作用域中无法查到该变量,引擎会抛出ReferenceError异常。
function foo(a) {
b = a
}
foo(2)
console.log(globalThis.b) // 2
在非严格模式下,如果引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域就会创建一个具有该名称的变量,并返回给引擎。
参考内容
- 《你不知道的JavaScript》上卷。
下一篇: 词法作用域