前言
- 编译型语言,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成,如java。
- 解释型语言,通过词法分析和语法分析得到抽象语法树(Abstract Syntax Tree)后,就可以开始解释执行了,如javascript。这里只是说明一下关于解析过程的原理,详细的解析过程(各种JS引擎还有不同)还需要更深一步的研究。
javascript的解释执行
JavaScript解释型语言会经过以下几个步骤: 读入源代码===>预解析===>执行===>结束,这里的预解析
就像一个编译过程,包括一下几个步骤:
- 词法分析
- 语法分析
- 预编译
- 执行
词法分析
词法分析器的作用,是将一行行的源码拆解成一个个词义单位(token)。所谓“词义单位”,指的是语法上不可能再分的、最小的单个字符或字符组合。
通过Esprima在线生成词法和语法分析树,可以在线查看对应的词法分析。
1 | var a=2; |
上面代码中,源代码经过词法分析后,返回一组词义单位,以及它们各自的词类。
语法分析
代码是给人读的,而js引擎要读的是抽象语法树。
语法分析将上一步生成的数组,根据语法规则,转为抽象语法树(Abstract Syntax Tree,简称AST)。如果源码符合语法规则,这一步就会顺利完成,生成一个抽象语法树;如果源码存在语法错误,这一步就会终止,抛出一个“语法错误”,并结束整个代码块的解析。
这个阶段主要做了两件事情:
- 确定作用域,根据静态作用域的特点,这个时候每个变量的作用域已经很明确了,不会在改变
- 记录每个作用域的所有变量和内嵌函数
1 | var a=2; |
上面代码生成的抽象语法树的json格式如下所示:
1 | { |
如果对抽象语法树感兴趣可以看下下面的链接:
AST explorer可以在线看到不同的parser解析js代码后得到的AST。
JavaScript AST visualizer 可以在线可视化的看到AST。
预编译
预编译是指javascript
引擎在执行一个函数时,会创建对应的执行上下文,它根据抽象语法树做的一些“准备工作”。这个过程包括一下几点:
- 创建变量对象
- 创建arguments对象,同名的实参,形参和变量之间是【引用】关系。
- 从语法分析树中复制作用域内的内嵌函数(functions)作为属性,key为函数name,属性值为函数的内存地址。
- 从语法分析树中复制作用域内所有变量(variables)作为属性,key为变量名称,此时变量值全部为
undefined
,若变量和函数存在同名,则跳过
- 创建作用域链,根据语法分析树中函数对应的作用域,结合当前环境的变量对象和上层环境的一系列变量对象组成
- 确定this指向
全局上下文的时候,变量对象就是全局对象window,this也志向window
执行
开始执行后,执行上下文中的变量对象转换为活动对象,可以执行一系列的操作。
- 变量赋值,查找规则是先找自身作用域,找不到就在作用域链上查找
- 函数引用
- 执行其他代码
- 执行结束,内存回收
代码执行结束后,函数内变量的生存周期取决于函数实例是否存在引用,如没有就销毁活动对象