什么是JavaScript的作用域
简述编译原理
分词/词法分析 这个过程将由字符组成的代码分解成对程序有意义的代码块,这些代码块被称为词法单元。 例如 var foo = 'bar'
通常会被分解为这些词法单元:var
、foo
、=
、'bar'
解析/语法分析 这个过程将词法单元转换成一个“由元素逐级嵌套组成的代表程序语法的树“,这个树被称为“抽象语法树”(AST)。 image 代码生成 将上边的抽象语法树转换为机器可执行代码
var foo = 'bar'
进行编译,然后做好执行它的准备,并且通常马上就会执行它。引擎、编译器、作用域在赋值操作中的配合
引擎:从头到尾负责整个JavaScript程序编译及执行过程 编译器:负责语法分析及代码生成 作用域:负责收集维护由所有变量组成的一系列查询
var foo = 'bar'
这段代码,大家很有可能认为是一句简单的声明。而事实上JavaScript执行时会将它分成两个完全不同的声明。编译器首先将这段代码分解成词法单元,然后解析为树结构。(在下一步代码生成时,处理这段代码的方式会跟预期有所不同) 遇到 var foo
,编译器会检查作用域是否已有同名变量存在。如果有的话编译器会忽略声明,继续编译。否则它会生成代码在当前作用域的变量集合中声明一个新的变量,命名为foo
接下来编译器会为引擎生成运行时所需代码,用来处理 foo = 'bar'
这个赋值操作。引擎运行时会首先查询当前作用域是否存在叫做 foo
的变量。如果有引擎则会使用这个变量,否则会一直向上层作用域查找。如果最终找到了 foo
这个变量,就会将'bar'
赋给它,否则抛出异常。
LHS查询 vs RHS查询
foo
来判断是否已经声明过。查找的过程由作用域来协助。在我们的例子中,引擎为变量 foo
进行的时LHS查询,还有另一个查找类型叫RHS查询。顾名思义,它们的意思是Left hand side 和 Right hand sideLHS:变量出现在赋值操作的左侧(查找赋值操作的目标是谁) RHS:变量在其他位置出现(查找值的源头)
console.log(foo)
foo
的引用就是RHS查询,这里没有赋予 foo
任何值,相反的,我们需要查找foo
的值,才能传递给log方法。foo = 'bar'
foo
的查询则是LHS查询,我们并不关心 foo
当前的值是什么, 只是想为这个赋值操作找到目标。function foo(a) {
console.log(a)
}
foo('bar')
最后一行 foo(...)
函数的调用需要对foo
进行RHS查询 → 找到foo
的值入参时存在隐式的 a = 'bar'
,需要对a
进行LHS查询console.log(a)
对a
进行RHS查询console.log(...)
本身也需要对console
对象进行RHS查询
作用域的嵌套
function foo(a) {
console.log(a + b)
}
var b = 258;
foo(369)
b
进行的RHS查询无法在 foo
内部完成,但可以在上一级的作用域中完成(在此例中是全局作用域)。ReferenceError
ReferenceError
。ReferenceError
。总结
作用域是一套规则,用于确定在何处以及如何查找变量。如果查找的目的是对变量赋值,会使用LHS查询;如果目的是获取变量的值,会使用RHS查询。 JavaScript引擎会在代码执行前对其进行编译。在这个过程中,像 var foo = 'bar'
这种声明会被分解成两个独立的步骤。var foo
在其作用域中声明新的变量。此操作在代码执行前进行。接下来 foo = 'bar'
会查询(LHS)变量foo
并对其赋值。LHS和RHS查询都会在当前执行作用域中开始,如果有需要(没有在当前作用域找到变量)就会向上级作用域继续查找目标变量,一直抵达全局作用域,无论找到与否都会停止。 不成功的RHS查找会导致抛出 ReferenceError
,不成功的LHS查找会导致自动隐式地创建一个全局变量(非严格模式下),或者抛出ReferenceError
(严格模式下)。
赞 (0)