代码真的被提拔了吗?

变量提拔 & 函数提拔

function f() {
    console.log(a); // ?
    var a = 1;
}
f();

简朴简朴,打印效果是 undefined。为啥?由于变量提拔,变量 a 的声明被提拔到当前作用域的顶部了。也就是可以设想成如许:

function f() {
    var a;
    console.log(a);
    a = 1;
}

别的,另有函数提拔,和变量提拔相似:

f1();

f2();

function f1() { console.log(1) }

function f2() { console.log(2) }

f1 和 f2 被提拔了,所以不会报错。许多文章中都会常常提到提拔这个观点。
然则,这是真的吗?JS 诠释器在实行代码时还顺带帮你更改一下代码的递次?想一想也以为不太可能嘛~那末究竟是咋回事捏?

可实行代码

JavaScript 的可实行代码(executable code)有三种,分别为 全局代码函数代码以及 eval 代码。鉴于 eval 不被引荐运用,咱就不睬它。

实行高低文

当实行全局代码时,会建立一个全局实行高低文。当实行一个函数的时刻,会建立一个函数实行高低文。全局实行高低文只会有一个,而函数实行高低文可以有许多许多个。JS 引擎会建立 实行高低文栈(execution context stack, ECS)去治理这些实行高低文。我们以函数实行高低文为例。

变量对象

关于一个实行高低文而言,可以笼统化为如许一个对象:

contextObject = {
    VariableObject,
    ScopeChain,
    thisValue
}

个中变量对象(Variable Object,VO)是包括与当前实行高低文相干的数据作用域,它存储着当前高低文中定义的变量声明、函数声明(注重:不包括函数表达式),别的函数实行高低文中还会有参数。

变量对象是一个笼统观点,在差别的实行高低文中会以差别的对象显现。比如在全局高低文中,变量对象就是全局对象自身(这也是为何我们可以经由历程全局对象的属性称号援用全局变量)。而关于函数实行高低文,是用运动对象(Activation Object,AO)来示意变量对象的,我们接见的就是运动对象上的属性。

实行历程

实行高低文中的代码会被分出两个阶段举行处置惩罚,分别为:

  • 进入实行高低文,即剖析建立阶段
  • 实行代码,即实行阶段

剖析建立阶段

此时进入实行高低文,但在最先实行代码之前。此时的变量对象:

  1. 假如是函数高低文,那末会经由历程函数的 arguments 属性初始化,并处置惩罚形参。此时变量对象中会包括一个 arguments 对象,以及一切的形参,并且会搜检是不是有与之对应的实参,有则值初始化为实参的值,没有的话就设为 undefined。
  2. 处置惩罚函数声明。最先处置惩罚函数声明,再次提示,函数表达式不会被处置惩罚。此时会搜检变量对象中是不是已有同名属性,假如有,则替代它,假如没有则建立属性,值初始化为对应的函数对象。
  3. 处置惩罚变量声明。与处置惩罚函数声明差别的是,假如变量对象中存在有同名属性,此时会跳过该变量不做任何事,即不会发生掩盖,假如没有则建立属性并初始化为 undefined。

相识了第一个阶段,那末来看个例子:

function f(a, b) {
    var c = 3;
    function d() {};
    var e = function() {};
    (function f() {});
}

f(1, 2);

当实行 f 函数时,进入实行高低文,我们可以形貌出此时的 AO:

AO = {
    arguments: {
        0: 1,
        1: 2,
        length: 2
    },
    a: 1,
    b: 2,
    c: undefined,
    d: reference to function() {},
    e: undefined,
}
// 注重:(function f() {}) 为函数表达式,不会被处置惩罚

实行阶段

实行阶段就相对简朴,代码递次实行,并且会依据代码去修正变量对象中的值,所以当函数实行完后的 AO 会是:

AO = {
    arguments: {
        0: 1,
        1: 2,
        length: 2
    },
    a: 1,
    b: 2,
    c: 3,
    d: reference to function() {},
    e: reference to function() {},
}

总结

综上所述:

  1. 全局高低文的变量对象等于全局对象;
  2. 函数高低文的变量对象会以 arguments 对象初始化;
  3. 在第一个阶段会顺次给变量对象增加形参(函数高低文)、函数声明、变量声明;
  4. 在第二个阶段,会修正变量对象中的属性值。

思考题

console.log(f); // ?

function f() {}
var f = 1;

打印效果会是一个函数对象。由于第一个阶段中先处置惩罚函数声明后处置惩罚变量声明,当处置惩罚变量声明时变量对象中已存在同名属性 f,不会做任何事直接跳过,所以 f 的值会是一个函数。

你也可以多找几道题尝试剖析以加深明白。固然,一样平常工作中照样直接想成提拔来得轻易些,只是愿望经由历程本文能让你相识到一些背地的事变。末了,假如文中有任何毛病或不足之处,还请指出~

    原文作者:JuddChan
    原文地址: https://segmentfault.com/a/1190000019332505
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞