要知道的
我们通常将JavaScript归类为“动态”或“解释执行”语言,但事实上它是一门编译语言
与传统的编译语言不同,js不是提前编译的,编译结果也不可以在分布式系统中进行移植,尽管如此,js引擎进行编译的步骤和传统的编译语言还是非常的相似,但他在某些环节可能会更复杂
传统编译语言的流程
在传统编译语言的流程中,如果程序中的一段源代码需要执行,那么在执行之前,它会经历三个步骤
分词/词法分析
将字符串分解成有意义的代码块(对编程语言来说)。这些代码块就被称为词法单元
var a = 2;
/**
* 词法分析后,就被分解成下面的词法单元:
* var 、 a 、 = 、 2 、 ;
* 至于空格是否被当做词法单元,这个取决于空格在这门语言中是否具有意义
**/
解析/语法分析
将词法单元流(数组)转换为一个抽象语法树(AST),这个树是由元素逐级嵌套并代表了程序语法结构的树
代码生成
将AST(抽象语法树)转换为可执行的代码,这个过程就是代码生成
这个过程和语言、平台信息等息息相关
简单的来说就是通过某种方法将var a = 2
的AST转换为一组机器指令,这个机器指令可以用来创建一个叫做a的变量(包括分配内存等),并将一个值存储在a中
js引擎中的编译器
比起那些编译过程只有上面三个步骤的语言,js引擎的编译器要比那些语言的编译器要复杂的多。比如在做语法分析和代码生成阶段会有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。
三个搭档
引擎
从头到尾的负责整个JavaScript程序的编译和执行
老大,负责掌管全局
编译器
负责语法分析及代码生成
任何js代码片段在执行前都要进行编译(通常就是在执行前)
// 编译器首先对 var a = 2 进行编译 ==》词法分析 和 语法分析
// 然后做好执行它的准备。(通常是马上就会执行它) ==》 代码生成
// js引擎开始执行代码
var a = 2
作用域
收集并维护所有声明的标识符(变量)组成的一系列查询,并根据一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
作用域其实就是一个规则,这个规则就是用来确定在何处以及如何查找变量(标识符的。
如果查找的目的是对变量进行赋值操作,那么就会使用LHS查询;
如果目的是获取变量的值,就会使用RHS查询
var a = 2 发生了什么
- 首先编译器会将这段程序分解为词法单元(词法分析),然后在将词法单元解析成一个树结构(语法分析)
- 代码生成:根据抽象语法树为js引擎执行
var a = 2
生成相关代码
编译器的小术语
LHS
变量出现在赋值操作的左边时会进行LHS查询
试图找到该容器的本身,并对它进行赋值
如果LHS查询找不到:在非严格模式下,全局作用域会创建一个具有该名称的变量,并将其返回给引擎;但是如果程序运行在严格模式下,则会抛出ReferenceError
异常
RHS
变量出现在赋值操作的非左侧时会进行RHS查询
试图取得该变量的源值
如果RHS查询在所有的嵌套作用域中都找不到所需的变量,引擎就会抛出ReferenceError
异常
无论是LHS查询还是RHS查询,都是先在当前的作用域中开始,如果没有找到所需的标识符,就会向上级的作用域继续查找,最后到达全局作用域,无论找到与否都会停止查找
引擎和作用域的对话
function foo(a){
console.log(a)
}
foo(2)
function foo(a){
var b = a
return a + b
}
var c = foo(2)
function foo(a){
console.log(a+b)
}
var b = 2
foo(2); // 4