什么是JavaScript解析引擎
JavaScript解析引擎(简称JavaScript引擎),是一个程序,是浏览器引擎的一个部分。
每一个浏览器的JavaScript解析引擎都不相同(因为每个浏览器编写JavaScript解析引擎的语言以及解析原理都不相同)。标准的JavaScript解析引擎会按照ECMAScript文档来实现。虽然每个浏览器的JavaScript解析引擎不同,但是他们最终的结果是相同的。
JavaScript解析引擎的作用
JavaScript解析引擎根据ECMAScript定义的语言标准来动态执行JavaScript字符串。动态解析JavaScript的过程分两个阶段:语法检查阶段和运行阶段
。
语法检查:包括词法分析、语法分析。
运行阶段:包括预解析、执行阶段。
JavaScript解析过程
在JavaScript引擎解析JavaScript代码的过程,如果遇到错误就会立即跳出当前的代码块,不会继续执行下面的代码,直接执行下一个代码块。
第一阶段:语法检查阶段
一、词法分析
核心:词法分析是将字符流(char stream)转换为记号流 (token stream)。
JavaScript引擎会将我们写的代码当成字符串分解成词法单元(token)。例如,var a = 2
,这段程序会被分解成:“var、a、=、2、;
” 五个token
。每个词法单元token
不可再分割。可以试试这个网站地址查看 token
二、语法分析
将词法分析阶段产生的token
, 转换成树状结构的 “抽象语法树(AST)
”
当语法检查正确无误之后,就可以进入运行阶段。
第二阶段:运行阶段
一 、预解析
全局代码处理过程:
- 读取整个源代码
- 先查找函数声明,再查找变量声明
- 将找到的函数和变量保存到一个全局对象中(window)
- 变量的值是undefined,函数的值指向该函数
函数处理过程:
- 读取整个函数的代码
- 将函数的参数添加到词法对象中
- 先查找函数声明,再查找变量声明
- 将找到的函数和变量保存到一个词法对象中
- 变量的值是undefined,函数的值指向该函数
二、执行阶段
JavaScript代码执行时,都会在执行上下文环境中进行。
执行上下文类型
JavaScript
执行上下文有三种:
全局执行上下文
当JS引擎执行全局代码的时候,会编译全局的代码并创建全局执行上下文,它会做两件事:1、创建一个全局对象(window
)。2、将this
指向该全局对象;全局上下文在整个页面声明周期有效,并且只要一个。
函数执行上下文
当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一个般情况下,函数执行结束之后,该函数执行上下文就会销毁。
eval执行上下文
调用eval
函数也会创建自己的执行上下文(eval函数容易导致恶意攻击,并且运行代码的速度比相应的替代方法慢,因此不推荐使用)
执行栈
用来存储代码运行时创建的所有执行上下文
当JS引擎开始执行第一行代码时,它会创建一个全局执行上下文并且将它压入执行栈中,每当引擎遇到一个函数调用时,首先会创建该函数的执行上下文,并且将其压入执行栈中。当函数执行结束时,执行上下文就会从执行栈中弹出。
如何创建执行上下文
执行上下文的创建分两个阶段:
- 创建阶段
- 执行阶段
创建阶段
执行上下文在创建阶段会做三件事:
- 绑定
this
- 创建词法环境
- 创建变量环境
绑定this
在全局执行上下文中,this
的指向时全局对象(window)。
值函数执行上下文中,this
的指向取决于该函数是谁调用的。
词法环境
词法环境是一种规范类型,基于ECMAScript代码的词法嵌套结构来定义标识符和具体变量和函数的关联。
简单来说词法环境
是一种持有标识符——变量
映射的结构(这里的标识符
指的是变量/函数的名字,而变量
是原始数据或对象的引用)。
在词法环境
的内部一般含有两个组件:环境记录器
、外部环境的引用
- 环境记录器:是存储变量和函数声明的实际位置
- 外部环境的引用:意味着它可以访问的父级词法环境(变量/函数的作用域)
因为区分全局执行上下文
和函数执行上下文
,所有词法环境
也有两种类型:
- 全局环境(在全局执行上下文中) 是没有外部环境引用的,所有外部环境的引用指向
null
。在环境记录器(对象环境记录器)
中,包含用户定义的全局变量,函数等,并且this
指向全局对象 - 函数环境(在函数执行上下文中) 外部环境的引用可能指向
全局环境
或函数环境
,环境记录器(声明式环境记录器)
中存储函数内部定义的变量、方法、还有一个arguments
对象和传递给函数的参数length
变量环境
变量环境也是一个词法环境,所有它有着词法环境定义的所有属性。在ES6中,词法环境和变量环境的一个不同之处就是前者存储函数声明和变量(let
和const
),而后者值存储var
声明的变量和函数。
JavaScript代码
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
执行上下文
//全局执行上下文
GlobalExectionContext = {
ThisBinding: <Global Object>,
//变量环境
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
//存储 let/const 变量绑定
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
//外部环境的引用
outer: <null>
},
// 词法环境
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
//存储并直接定义 var 变量
c: undefined,
}
outer: <null>
}
}
//函数执行上下文
FunctionExectionContext = {
ThisBinding: <Global Object>,
//词法环境
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 词法环境生成的arguments属性
Arguments: {0: 20, 1: 30, length: 2},
},
//外部引用指向全局环境
outer: <GlobalLexicalEnvironment>
},
// 变量环境
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
//存储并直接定义 var 变量
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
注意 let
和 const
定义的变量没有值,但 var
定义的变量被设成了 undefined
。
执行阶段
这是整篇文章中最简单的部分。在此阶段,完成对所有变量的分配,最后执行代码.
注意 在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到let
变量的值,它会被赋值为 undefined