JavaScript引擎在代码执行之前会先进行如下操作:

  1. 先进行分词/词法剖析将语句宰割成词法单元 token,在对以后的整个作用域剖析实现后,JS引擎会将 token进行解析/语法分析翻译成 AST(形象语法树)
  2. 预编译(预处理)
  3. 边解释边执行(不是纯解释,还有JIT编译,这里不开展了)

预编译(预处理)

有人说JavaScript没有预编译,是属于语法分析的一部分,有人说有词法,语法和代码生成就曾经属于编译了,只是不须要提前编译的,而是在执行前的几毫秒才编译。所以JavaScript到底有没有预编译或者这些步骤属不属于预编译(预处理)大佬们的意见也不同,咱们就不探讨了,暂且把语法分析生成AST后到执行前的一些处理过程称之为“ 预编译 ”把。

  1. 全局预编译

    1. 创立 Global Object 全局上下文对象
    2. 找变量申明,将变量名绑定为GO的一个属性,值为undefined
    3. 找函数申明,将函数名绑定为GO的一个属性,值为函数体
    4. 执行全局代码
  2. 部分预编译

    1. 创立 Activation Object 函数上下文对象
    2. 找行参,将行参名绑定为AO的一个属性,值为 undefined
    3. 找变量申明,将变量名绑定为AO的一个属性,值为 undefined
    4. 实参值赋值给形参
    5. 找函数申明,将函数名绑定为AO的一个属性,值为 函数体
    6. 函数生成[[scope]]属性,值为一个数组,数组的第一项是本函数的AO,下一项为外层函数的AO,始终往外层,直到最初一项为GO
    7. 执行函数代码

申明晋升,作用域链

下面预编译的前半部分就是所谓的申明晋升,我有文章独自讲了申明晋升就不赘述了,详情点击这里。

函数执行前会生成`[[scope]]`属性,值为一个数组,数组的第一项是本函数的 AO下一项为外层函数的 AO,始终往外层,直到最初一项为 GO

AO是函数的作用域,而[[scope]]这样的链式数组则是函数的作用域链,当函数进行值查问RHS时会先在[[scope]]中的第一项中寻找,如果没有找到则查找第二层,以此类推直到查找到GO为止。

闭包

函数FN执行完结后,这个函数FNAO的生命周期就应该完结了,须要被销毁。然而有非凡状况,尽管说是非凡状况,然而开发中十分常见,就是函数FN执行完结后return了一个外部函数A并被内部保留后,原本应该被销毁的函数FNAO便无奈被销毁,因为函数A的[[scope]]保留着函数FNAO作为本人原型链的一部分,只有保留函数A的变量不被销毁或者变量所在的AO不被销毁则函数FNAO永远不会被销毁,并且变量保留的函数A能够始终拜访函数FN中的变量和值,这样的景象叫做闭包,这样的闭包大量呈现会导致内存透露或者加载过慢。

var Variable_1 = 1;function FN() {    var Variable_2 = 2;    function A() {        var Variable_3 = 3        console.log(Variable_2);    }    return A;}var Function_A = FN();FnVariable(); // 承受 函数A 的变量 Function_A 通过调用能够输入 FN 中的 Variable_2