js引擎解析过程
语法检查
语法检查是 JavaScript 解析器的工作之一,包括 Î词法分析和语法分析
词法分析
把 JavaScript 代码(字符创)逐字转换为标记流
例如:
1 | // a = (b - c); |
转换为:
1 | NAME "a" |
语法分析
语法分析:JavaScript 语法分析器在经过词法分析后,将记号流按照 ECMAScript 标准把词法分析所产生的记号生成语法树 AST。
运行阶段
预解析
- 将 JavaScript 引擎将语法检查正确后生成的语法树复制到当前执行上下文中。
- JavaScript 引擎会对语法树当中的变量声明、函数声明以及函数的形参进行属性填充( 声明提升)。
执行上下文包括:变量对象、作用域链、this
- 变量对象(Variable Object):由变量声明(var declaration)、函数声明(function declaration)、参数(arguments)构成。
- 作用域链(Scope Chain): 变量对象+父级作用域
- this :一旦进入代码执行阶段的上下文中(预编译后的代码执行阶段),this 的值就不会变了。
变量对象/活动对象(VO/AO)属性填充
变量对象/活动对象(VO/AO)填充的顺序:函数的形参->函数声明->变量声明;在变量对象/活动对象(VO/AO)中权重高低也按照函数的形参->函数声明->变量声明顺序来。
函数的形参:执行上下文的变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为 undefined。
函数声明:执行上下文的变量对象的一个属性,属性名和值都是函数对象创建出来的;如果变量对象已经包含了相同名字的属性,则会替换它的值。
变量声明:执行上下文的变量对象的一个属性,其属性名即为变量名,其值为 undefined;如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的函数声明的属性,该声明会被忽略掉,但其包含的赋值操作不会忽略。
1
2
3
4
5
6
7
8// 函数提升优先级高于变量提升,所以b取函数
var a = 1
function b(a) {
alert(a)
}
var b // 被忽略,因为函数声明优先级高于变量声明
alert(b) // function b(a) { alert(a); }
b() //undefined
代码执行
经过“预解析”创建执行上下文之后,就进入执行代码阶段,VO/AO 就会重新赋予真实的值,“预解析”阶段赋予的 undefined 值会被覆盖。
此阶段才是程序真正进入执行阶段,Javascript 引擎会一行一行的读取并运行代码。此时那些变量都会重新赋值。
假如变量是定义在函数内的,而函数从头到尾都没被激活(调用)的话,则变量值永远都是 undefined 值。
进入了执行代码阶段,在“预解析”阶段所创建的任何东西可能都会改变,不仅仅是 VO/AO,this 和作用域链也会因为某些语句而改变,后面会讲到。
作用域链(Scope Chain)
作用域链是处理标识符时进行变量查询的变量对象列表,每个执行上下文都有自己的变量对象:对于全局上下文而言,其变量对象就是全局对象本身;对于函数而言,其变量对象就是活动对象。
作用域链和执行上下文的关系
在 Javascript 中只有函数能规定作用域,全局执行上下文中的 Scope 是全局上下文中的属性,也是最外层的作用域链。
函数内部的属性(作用域)是在“预解析”的时候就已经存在的了,它包含了所有上层变量对象,并一直保存在函数中。就算函数永远都没被激活(调用),也都还是存在函数对象上。
执行上下文定义的 Scope 属性变化过程
执行上下文中的[AO]是函数的活动对象,而[[Scope]]则是该函数属性作用域。当前函数的 AO 永远是在最前面的,保存在堆栈上,而每当函数激活的时候,这些 AO 都会压栈到该堆栈上,查询变量是先从栈顶开始查找,也就是说作用域链的栈顶永远是当前正在执行的代码所在环境的 VO/AO(当函数调用结束后,则会从栈顶移除)。
通俗点讲就是:JavaScript 解释器通过作用域链将不同执行位置上的变量对象串连成列表,并借助这个列表帮助 JavaScript 解释器检索变量的值。作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系。当 JavaScript 解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象为止,如果没有找到值,则传递一个特殊的 undefined 值。
是不是又想到了一条 JavaScript 高效准则:为什么说在该函数内定义的变量,能减少函数嵌套能提高 JavaScript 的效率?因为函数定义的变量,此变量永远在栈顶,这样子查询变量的时间变短了。
作用域链特性
闭包是作用域链特性的应用
保证查询有序的访问所有变量和函数
作用域链感觉就是一个 VO 链表,当访问一个变量时,先在链表的第一个 VO 上查找,如果没有找到则继续在第二个 VO 上查找,直到搜索结束,也就是搜索到全局执行环境的 VO 中。这也就形成了作用域链的概念。