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(事件轮询)

评论