js引擎的预编译和执行
var 的变量提升底层原理
JavaScript引擎,不是逐条解释执行javascript代码,而是按照代码块一段段解释执行,
所谓代码块就是script标签分割的代码块。
js引擎的工作方式分为:预编译和执行代码两个阶段。
1)常见的编译型语言编译阶段:词法分析(生成词法单元)——>语法分析(抽象语法树)——>(语义检查,代码优化)——>代码生成
2)对于解释型语言来说,通过词法分析和语法分析得到抽象语法树之后就开始执行了,在JavaScript
解释器在构造语法树的时候,如果无法构造,就会报语法错误,并结束整个代码块的执行。
而在整个编译阶段,会把“一等公民”function和var创建的变量进行提升。(其中函数提升在变量
提成之前)
3)JavaScript语法采用的是词法作用域,也就是javascript的变量和函数作用域是在定义时
决定的,函数调用时决定的是执行期上下文和作用域链,所以js解释器只需要静态分析就能确定每个
变量、函数的作用域,这种作用域也称为静态作用域。
执行上下文
全局执行上下文:默认的上下文,任何不再函数内部的代码都在全局上下文中。它会执行两
件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。
一个程序只会有一个全局执行上下文。函数执行上下文:每当函数被调用时,都会为该函数创建一个新的上下文。
Eval 函数执行上下文:执行在 eval 函数内部的代码也会有属于它自己的执行上下文。
执行栈:JavaScript 引擎会以栈的方式来处理多个执行期上下文其他语言叫“调用栈”,
类似于数据结构的栈 LIFO(后进先出),用来存储代码运行时创建的所有执行上下文。
栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。当 JavaScript 引擎第一次遇见脚本时,它会创建一个全局的执行上下文并且压入当前
执行栈,每当引擎遇到一个函数调用,会为该函数创建一个新的执行上下文并压入栈的顶部。引擎会执行那些执行上下文位于栈顶的函数,当函数执行结束时,执行上下文从栈顶
弹出,控制流程到达当前栈中的下一个上下文。当函数执行时,会创建一个成为 执行期上下文的内部对象。
执行期上下文就是 js 代码被解析和执行时的运行环境,函数每次执行都会创建一个独一无二的执行上下文,所以多次调用会产生
多个执行上下文,当函数执行完毕,所产生的执行上下文被销毁。执行上下文的生命周期包括三个阶段:
创建阶段->执行阶段->挥手阶段创建阶段会创建变量对象(Variable Object),建立作用域链,确定 this 指向 1. 创建 Scope chain 2. 创建 AO 3. 设置 this 的值
创建 AO 主要做了以下事情: 1. 创建 AO 对象 2. 形参和变量声明存储到 AO 对象 //赋值为 undefined 3. 将形参和实参相统一 4. 函数声明的函数名作为 AO 对象的 key,函数体作为 value执行阶段会完成变量赋值,函数引用,以及执行其他代码。
AO(Active Object) & VO(Variable Object)的区别?
- VO:未进入执行上下文执行阶段之前,变量对象中的属性都不能访问。
- AO:进入执行阶段之后,变量对象转变为活动对象,里面的属性都能访问了。
- 它们都是同一个对象,只是处于执行上下文的不同生命周期,而且只有处于
函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。
如果函数引用了外部变量的值,则 JavaScript 引擎会为改函数创建一个闭包体(closure),
闭包体是一个完全封闭和独立的作用域,他不会在函数调用完毕后就被 js 引擎当作垃圾
进行回收,闭包体可以长期存在。
- 执行上下文总结:
- 单线程
- 同步执行,只有栈顶的上下文处于执行中,而其他上下文需要等待
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈,而且其他所有上下文环境
都可以直接访问全局上下文的属性 - 函数的执行上下文的个数没有限制
- 每次某个函数被调用,就会有新的执行上下文为其创建,即使是调用的自身函数,也是如此。
JS 执行机制
同步和异步任务分别进入不同的执行“场所”,同步的进入主线程,异步的进入Event Table
并注册函数。
当指定的事情完成时(例如定时器执行完毕,获取数据结束等),Event Table会将这个函数移入Event Queue
主线程内的任务执行完毕为空,会去Event Queue(事件队列)读取对应的函数,进入主线程执行。
上述过程不断重复,称为Event Loop(事件轮询)