【学习日记】JavaScript中的预编译

336 阅读4分钟

声明提升

直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的。但实际上这并不完全 正确,有一种特殊情况会导致这个假设是错误的。

考虑一下代码:

a=2
var a
console.log(a)  //2

很多人可能会认为以上代码输出的是undefined,因为var a声明在了a=2之后,所以让人自然而然的认为变量被重新赋值成undefined,但他输出的其实是2。

考虑另外一段代码:

console.log(a) //undefined
var a=2

同上段代码一样可能会认为这段代码会报错,但结果其实是undefined。

讲到这我们就要引出这段内容的主角了,变量的声明提升。如何解释以上问题,我们需要了解JavaScript这门语言的编译规则在我以前的文章里也解释过,我们回忆一下,引擎会 在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的 声明,并用合适的作用域将它们关联起来。所以对于上述代码应该会变成这样:

第一段:

var a
a=2
console.log(a)  //2

第二段:

var a
console.log(a)
a=2

因此,打个比方,这个过程就好像变量声明从它们在代码中出现的位置被“移动” 到了最上面。这个过程就叫作提升。 然而不仅仅是变量会提升函数也会:

foo()
function foo(){
  console.log(a)   //undefined
  var a=2
}

以上代码通过编译会变成:

function foo() { 
  var a; 
  console.log( a ); // undefined 
  a = 2;
} 
foo();

以上就区别了在声明提升中变量声明,声明提升;函数声明,整体提升。

函数中的预编译

  1. 创建函数的执行上下文对象 AO {Activation Object}
  2. 找形参和变量声明,然后将形参和变量名作为 AO 的属性,值为undefined
  3. 将实参和形参统一
  4. 在函数体内找函数声明,将函数名作为AO的属性名,值赋予函数体

拿一段代码举例:

function fn(a){
    console.log(a);   //function a (){}
    var a=123
    console.log(a)    // 123
    function a(){}
    console.log(a)    // 123
    var b=function(){}
    console.log(b)    // function (){}
    function d(){}
    var d=a
    console.log(d)   // 123
}
fn(1)

我们先按照第一步创建AO对象:

 AO:{
    
 }

然后第二步找形参和变量声明:

AO:{
    a=undefined
    b=undefined
    d=undefined
 }

第三步将实参和形参统一:

AO:{
    a=undefined->1
    b=undefined
    d=undefined
 }

最后第四步找函数声明:

AO:{
    a=undefined->1->function a(){}
    b=undefined
    d=undefined->function d(){}
 }

预编译完开始执行函数就能得到:

image.png

这就是函数中预编译的步骤

全局的预编译

  1. 创建全局执行上下文对象 GO {Global Object}
  2. 找变量声明,变量名作为GO的属性名,值为undefined
  3. 在全局找函数声明,函数名作为GO的属性名,值为函数体

全局的预编译和函数中的预编译步骤大致差不多只缺少一个将形参和实参统一,接下来我们也用一个例子来讲解这步骤吧:

global=100
function fn(){
    console.log(global)  //undefined
    global=200
    console.log(global)  //200
    var global=300
}
fn()
var global;
console.log(global)   //100

按照第一步开始创建GO:

GO:{
  
}

第二步找变量声明:

GO:{
  globalundefined
}

第三步找函数声明:

GO:{
  globalundefined->100
  fn:function(){}
}

做完这开始运行运行到fn()调用fn函数又执行fn函数的预编译步骤按函数的预编译执行我们这直接写出来:

AO:{
  global:undefined
}

开始执行函数体内的代码第一个console.log(global)输出undefined,然后一个global=200,就要修改global的值,导致第二个console.log(global)输出200,最后var global=300给global赋值为300;但函数体内的变量不影响全局变量所以最后AO对象应该为:

AO:{
  globalundefined->200->300
}

然后执行完调用函数fn,var global;:这里又出现了一次 global 的声明,但是因为在执行 fn 函数时已经给全局的 global 分配了值100,这个声明不会改变 global 的值,它实际上是一个多余的声明,因为 global 已经在第一行被声明和赋值了。所以最后一个console.log(global)输出100。

小结

通过这几个例子我们大概能理解函数中的预编译和全局的预编译,理解预编译的过程对我们以后学习代码,编写代码和维护代码都有至关重要。以上内容如有错误请指点,小白一枚多多包涵