范围的概念
现代编程语言最基本的特性之一是能够在变量中存储值以供以后使用和修改。 正是这个函数为程序带来了状态。
在 中,作用域是一组精心设计的用于存储变量的规则。
简述编译原理
通常我们会将其归类为“动态”或“解释”语言,但它实际上是一种编译语言。 与传统的编译语言不同,它不会提前编译,并且编译结果在分布式系统中不可移植。
例如V8引擎,为了提高代码的运行性能,在运行之前会将其编译为本地机器码,然后执行机器码,以达到提高速度的目的。
对于只有三个步骤的语言来说,引擎比编译器复杂得多。 例如,在语法分析和代码生成阶段有特定的步骤来优化运行性能,包括优化冗余元素。
大多数情况下,编译发生在代码执行的前几微秒内,任何代码片段都必须在执行前进行编译。 因此,编译器首先编译 var foo = 'bar',然后准备好执行它,通常是立即执行。
赋值操作中引擎、编译器和作用域的配合
对于代码 var foo = 'bar',您可能认为这是一个简单的语句。 事实上,执行时会将其分成两个完全不同的语句。
编译器首先将此代码分解为标记单元,然后将其解析为树结构。 (在下一步的代码生成中,该代码的处理方式将与预期不同)
当遇到 var foo 时,编译器会检查作用域中是否已经存在同名变量。 如果有声明,编译器将忽略该声明并继续编译。 否则,它将生成代码以在当前作用域的变量集合中声明一个名为 foo 的新变量。
接下来,编译器将生成引擎处理 foo = 'bar' 赋值操作所需的运行时代码。
当引擎运行时,它会首先查询当前作用域中是否存在名为 foo 的变量。 如果有引擎,就会使用这个变量,否则它会一直查找上层范围。
如果最终找到变量 foo,则会将“bar”赋值给它,否则会抛出异常。
总结:变量的赋值会执行两个动作:首先,编译器在当前作用域中声明该变量(如果该变量还没有被声明过); 然后运行时引擎在作用域中搜索该变量,如果找到则为其分配一个值。
左轴查询与右轴查询
当引擎执行编译器生成的代码时,它会查找foo来判断它是否已经被声明。 搜索过程由范围辅助。 在我们的示例中,引擎对变量 foo 执行 LHS 查询。 还有另一种搜索类型,称为 RHS 查询。 顾名思义,它们的意思是“左手边”和“右手边”
// 考虑下边的代码
console.log(foo)
本例中对 foo 的引用是 RHS 查询。 没有为 foo 分配任何值。 相反,我们需要找到 foo 的值,然后才能将其传递给 log 方法。
// 相比之下
foo = 'bar'
这里对 foo 的查询是 LHS 查询。 我们不关心 foo 的当前值是多少,我们只想找到这个赋值操作的目标。
// 再分析下边的代码
function foo(a) {
console.log(a)
}
foo('bar')
该代码同时包含LHS查询和RHS查询
最后一行中对 foo(...) 函数的调用需要对 foo 进行 RHS 查询 → 查找 foo 的值
输入参数时隐含a = 'bar',a需要LHS查询。
.log(a) 对 a 执行 RHS 查询
.log(...) 本身也需要对对象进行 RHS 查询
范围嵌套
我们在文章开头说过,作用域是一组通过名称查找变量的规则。 实际情况中,需要同时考虑多个范围。
当一个块或函数嵌套在另一个块或函数中时,就会发生作用域嵌套。 因此,当在当前作用域中没有找到目标变量时,就会逐层查找,直到全局作用域。
// 考虑以下代码
function foo(a) {
console.log(a + b)
}
var b = 258;
foo(369)
b 的 RHS 查询不能在 foo 内部完成,但可以在上一级作用域(本例中为全局作用域)中完成。
LHS和RHS查询将在范围内逐层搜索,直到找到(或到达全局范围)。
上一节提到,LHS和RHS会在作用域中逐层寻找变量,但是如果在全局作用域中仍然找不到变量怎么办?
这时,区分LHS和RHS查询的意义就显现出来了。
如果 RHS 查询没有在所有嵌套作用域中找到所需的变量,引擎将抛出异常。
如果 LHS 查询在所有嵌套作用域中都没有找到所需的变量,则引擎会在全局作用域中创建一个具有该名称的变量并将其返回给引擎。
注意:严格模式是在 ES5 中引入的。 与普通模式相比,严格模式的区别之一是全局变量是自动或隐式创建的。 因此,当严格模式下LHS查询失败时,全局变量将不会被创建和返回,引擎也会抛出异常。
总结
-EOF-