引擎
从头到尾负责整个JavaScript程序的编译及执行过程。
编译器
负责语法分析及代码生成等
作用域
负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问。
其实作用域就是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,就会使用LHS查询,如果目的是获取变量的值,就会使用RHS查询,赋值操作符会导致LHS查询, = 操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。
JavaScript 中的 LHS 和 RHS
LHS 和 RHS 是 JavaScript 引擎的两种查找类型,含义是赋值操作的左侧与右侧。
LHS: 对哪个赋值就对哪个进行 LHS 引用,可以理解为赋值操作的目标。
RHS: 需要获取哪个变量的值就对哪个变量的值进行 RHS 引用,理解为赋值操作的源头。
两者不同的是:如果RHS获取没有生命的变量 就会抛出错误,而LHS就会创建一个变量(没有开启严格模式的情况下),如果开启了严格模式就会抛出一个和RHS类似的错误。
// 例子function foo(a) { console.log(a); // 2}foo(2);
书本原文
最后一行 foo(..) 函数的调用需要对 foo 进行 RHS 引用,意味着“去找到 foo 的值,并把它给我”。并且 (..) 意味着 foo 的值需要被执行,因此它最好真的是一个函数类型的值!
代码中隐式的 a = 2 操作可能很容易被你忽略掉。这个操作发生在 2 被当作参数传递给 foo(..) 函数时,2 会被分配给参数 a。为了给参数 a(隐式地)分配值,需要进行一次 LHS 查询。
这里还有对 a 进行的 RHS 引用,并且将得到的值传给了 console.log(..)。console. log(..) 本身也需要一个引用才能执行,因此会对 console 对象进行 RHS 查询,并且检查 得到的值中是否有一个叫作 log 的方法。最后,在概念上可以理解为在 LHS 和 RHS 之间通过对值 2 进行交互来将其传递进 log(..) (通过变量 a 的 RHS 查询)。假设在 log(..) 函数的原生实现中它可以接受参数,在将 2 赋 值给其中第一个(也许叫作 arg1)参数之前,这个参数需要进行 LHS 引用查询。
作用域嵌套
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域嵌套。
在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,知道找到该变量,或者抵达最外层的作用域为止。
function foo(a) {
console.log( a + b );
}
var b = 2;
foo(2); // 4
// 对b进行的RHS进行引用无法在foo内部完成,可以在全局作用域中完成。
遍历嵌套作用域链的规则很简单:引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当前抵达最外层的全局作用域时,无论找没找到,查找过程都会停止。
词法作用域
词法作用域就是定义在词法阶段的作用域,也就是词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。
1、全局作用域:foo
2、foo创建的作用域:a、bar、b。
3、bar创建的作用域:c
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置来决定的。
欺骗词法
JavaScript中有两种机制来实现。
eval
这段代码实际在foo(...)中内部创建一个变量b,并遮盖了外部作用域的同名变量
默认情况下,eval(...)中执行的代码包含有一个或者多个声明,就会对eval(...)所处的词法作用域进行修改,也可以通过一些技巧可以间接滴哦用eval(...)来使其运行在全局作用域中,并对全局作用域进行修改。无论什么情况都可以在运行期修改书写期的词法作用域。
with
wuth通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
var obj = { a: 1, b: 2, c: 3 };// 单调乏味的重复 "obj" obj.a = 2; obj.b = 3; obj.c = 4; // 简单的快捷方式with (obj) { a = 3; b = 4; c = 5; }