javascript笔记之JS解释执行过程

前言

  1. 编译型语言,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成,如java。
  2. 解释型语言,通过词法分析和语法分析得到抽象语法树(Abstract Syntax Tree)后,就可以开始解释执行了,如javascript。这里只是说明一下关于解析过程的原理,详细的解析过程(各种JS引擎还有不同)还需要更深一步的研究。

javascript的解释执行

JavaScript解释型语言会经过以下几个步骤: 读入源代码===>预解析===>执行===>结束,这里的预解析就像一个编译过程,包括一下几个步骤:

  • 词法分析
  • 语法分析
  • 预编译
  • 执行

词法分析

词法分析器的作用,是将一行行的源码拆解成一个个词义单位(token)。所谓“词义单位”,指的是语法上不可能再分的、最小的单个字符或字符组合。

通过Esprima在线生成词法和语法分析树,可以在线查看对应的词法分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var a=2;
function addA() {
return a;
}

// 词法分析后的结果
[
{"type":"Keyword","value":"var"},
{"type":"Identifier","value":"a"},
{"type":"Punctuator","value":"="},
{"type":"Numeric","value":"2"},
{"type":"Punctuator","value":";"},
{"type":"Keyword","value":"function"},
{"type":"Identifier","value":"addA"},
{"type":"Punctuator","value":"("},
{"type":"Punctuator","value":")"},
{"type":"Punctuator","value":"{"},
{"type":"Keyword","value":"return"},
{"type":"Identifier","value":"a"},
{"type":"Punctuator","value":";"},
{"type":"Punctuator","value":"}"}
]

上面代码中,源代码经过词法分析后,返回一组词义单位,以及它们各自的词类。

语法分析

代码是给人读的,而js引擎要读的是抽象语法树。

语法分析将上一步生成的数组,根据语法规则,转为抽象语法树(Abstract Syntax Tree,简称AST)。如果源码符合语法规则,这一步就会顺利完成,生成一个抽象语法树;如果源码存在语法错误,这一步就会终止,抛出一个“语法错误”,并结束整个代码块的解析。

这个阶段主要做了两件事情:

  • 确定作用域,根据静态作用域的特点,这个时候每个变量的作用域已经很明确了,不会在改变
  • 记录每个作用域的所有变量和内嵌函数
1
2
3
4
var a=2;
function addA() {
return a;
}

上面代码生成的抽象语法树的json格式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "Literal",
"value": 2,
"raw": "2"
}
}
],
"kind": "var"
},
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "addA"
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "Identifier",
"name": "a"
}
}
]
},
"generator": false,
"expression": false,
"async": false
}
],
"sourceType": "script"
}

如果对抽象语法树感兴趣可以看下下面的链接:

Esprima在线生成词法结构和语法分析树

AST explorer可以在线看到不同的parser解析js代码后得到的AST。

JavaScript AST visualizer 可以在线可视化的看到AST。

预编译

预编译是指javascript引擎在执行一个函数时,会创建对应的执行上下文,它根据抽象语法树做的一些“准备工作”。这个过程包括一下几点:

  • 创建变量对象
    • 创建arguments对象,同名的实参,形参和变量之间是【引用】关系。
    • 从语法分析树中复制作用域内的内嵌函数(functions)作为属性,key为函数name,属性值为函数的内存地址。
    • 从语法分析树中复制作用域内所有变量(variables)作为属性,key为变量名称,此时变量值全部为undefined,若变量和函数存在同名,则跳过
  • 创建作用域链,根据语法分析树中函数对应的作用域,结合当前环境的变量对象和上层环境的一系列变量对象组成
  • 确定this指向

全局上下文的时候,变量对象就是全局对象window,this也志向window

执行

开始执行后,执行上下文中的变量对象转换为活动对象,可以执行一系列的操作。

  • 变量赋值,查找规则是先找自身作用域,找不到就在作用域链上查找
  • 函数引用
  • 执行其他代码
  • 执行结束,内存回收

代码执行结束后,函数内变量的生存周期取决于函数实例是否存在引用,如没有就销毁活动对象

参考

javascript的词法作用域