文章来源:https://wflynn.cn/pages/0b0205/ 作者::Miofly
通常将 JavaScript 归类为“动态”或“解释执行”语言,但事实上它是一门编译语言。
一般会经历三个步骤,统称为“编译”。
这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token
)。例如,考虑程序 var a = 2;
。这段程序通常会被分解成为下面这些词法单元:var
、a
、=
、2
、;
。空格是否会被当作词法单元,取决于空格在这门语言中是否具有意义。
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树 ”(Abstract Syntax Tree
,AST
)。var a = 2
; 的抽象语法树中可能会有一个叫作 VariableDeclaration
的顶级节点,接下 来是一个叫作 Identifier
(它的值是 a
)的子节点,以及一个叫作 AssignmentExpression
的子节点。AssignmentExpression
节点有一个叫作 NumericLiteral
(它的值是 2
)的子节点。
将 AST
转换为可执行代码的过程称被称为代码生成。这个过程与语言、目标平台等息息相关。抛开具体细节,简单来说就是有某种方法可以将 var a = 2
; 的 AST
转化为一组机器指令,用来创建一个叫作 a
的变量(包括分配内存等),并将一个值储存在 a
中。
JavaScript
的编译过程不是发生在构建之前的。 对于JavaScript
来说,大部分情况下编译发生在代码执行前的几微秒(甚至更短!)的时间内。在我们所要讨论的作用域背后,JavaScript
引擎用尽了各种办法(比如 JIT,可以延迟编译甚至实施重编译)来保证性能最佳。 简单地说,任何JavaScript
代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript
编译器首先会对var a = 2
; 这段程序进行编译,然后做好执行它的准备,并且通常马上就会执行它。
JavaScript
程序的编译及执行过程。编译器首先会将这段程序分解成词法单元,然后将词法单元解析成一个树结构,也就是上面提到的分词以及解析。
编译器开始进行代码生成时的处理如下
var a
,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a
。a = 2
这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a
的 变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量如果引擎最终找到了 a
变量,就会将 2
赋值给它。否则引擎就会抛出一个异常!总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。
最简单的理解可以概括如下:如果查找的目的是对变量进行赋值,那么就会使用 LHS
查询;如果目的是获取变量的值,就会使用 RHS
查询。
var a = 2
,很显然是将 2
赋值给变量 a
,所以这是 LHS
查询
(a)
中的 a 我们要获取 a
这个变量的值,这时就需要执行 RHS
查询console.log()
本身也需要一个引用才能执行,这时候我们还需要获取 console
的引用,所以要对 console
执行 RHS
查询。当我们查询到 console
的引用后,会检查得到的值中是否有一个叫做 log
的方法
这里不会再对 log
进行 RHS
查询。因为对 console
查询完毕后,对象属性访问规则会接管对 log
属性的访问。对象会接管其下面的属性的访问function foo(fnn) {
console.log(fnn);
};
foo('girl');
foo(...)
我们需要找到它的引用,这时就需要执行 RHS
查询girl
这个值赋值给了函数参数中的 fnn
这个变量,这时就需要执行 LHS
查询console.log(fnn)
的分析就和例一一样,对 fnn
进行 RHS
查询,对 console.log
进行 RHS
查询function foo(fnn) {
var wfly = fnn;
return fnn + wfly;
}
var c = foo('girl');
var c = foo('girl')
中, foo(...)
我们需要找到它的引用,这时就需要执行 RHS
查询,然后将值赋值给 c
,执行 LHS
查询girl
这个值赋值给了函数参数中的 fnn
这个变量,这时就需要执行 LHS
查询var wfly = fnn
; 首先,获取 fnn
变量的值需要执行一次 RHS
查询,接着将 fnn
变量的值赋值给 wfly
变量,执行 LHS
查询return fnn + wfly
需要对 fnn
执行 RHS
查询,同样的对 wfly
也要执行 RHS
查询所以共计执行了
4
次RHS
查询,3
次LHS
查询