总所周知,javascript是一门解释型的脚本语言,其主要的步骤为解释一行,执行一行,但这执行第一行代码之前,javascript会有一个预编译的步骤。

大家有没有发现,有时我们在变量声明的前面使用该变量,不会报错。这种变量提升的情况也是属于预编译的中所做的。

JavaScript的预编译分为全局预编译和局部预编译(函数预编译)

全局预编译步骤

  1. 创建GO对象(Global Object)全局对象。
  2. 找变量声明,将变量名作为GO属性名,值赋为undefined。
  3. 查找函数声明,将函数名作为GO属性,值赋予函数体。

局部预编译步骤

  1. 创建AO对象(Activation Object)执行期上下文。
  2. 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined。
  3. 将实参值赋值给对应形参属性。
  4. 在函数体里面找函数声明,值赋予函数体。

 

首先我们先看看全局预编译  ↓

1 console.log(`1 -> a:${a} \n b:${b} \n c:${c}`)
 2 /* 声明 */
 3 var a                 // 变量声明
 4 function b() {
 5    // b的函数体
 6 }       // 函数声明方式
 7 var c = function() {
 8   // c的函数体
 9 } // 函数表达式方式
10 console.log(`2 -> a:${a} \n b:${b} \n c:${c}`)
11 /* 赋值 */
12 a = 1
13 b = 2
14 c = 3
15 console.log(`3 -> a:${a} \n b:${b} \n c:${c}`)

上面代码声明了变量a,已经用两种函数创建方式创建了函数b和c,并在代码的不同地方打印出abc的值。

猜猜看这3个地方的变量都会是什么。

javascript前端 javaScript前端编译_赋值

如果在不知道js预编译的情况下,看到这样的打印结果确实很奇怪,接下来我们来通过全局预编译的步骤来解读下这段代码。

首先位于全局作用域的代码,也就是所有的代码会进行全局预编译。

一、创建一个Go对象。

Go {
}

二、找变量声明,并将该变量名作为上面创建Go对象的属性名,属性值赋值为undefined;变量声明为第3行的变量a,以及第7行的变量c。

Go {
  a: undefined,
  c: undefined
}

三、找函数声明,将函数名作为上面创建GO对象的属性名,属性值赋予函数体;函数声明为第4行的函数b。

Go { 
  a: undefined,
  b: function(){},
  c: undefined 
}

四、目前预编译已经完成,接下来开始逐行执行代码;第1行打印abc及第三步操作后的Go对象的属性。(a为undefined,b为函数,c为undefined)

五、第2~6行的变量声明与函数声明已经在预编译操作过,第7行的变量c声明在预编译操作过,但是,第7~9行还存在变量赋值的操作即将函数赋值给变量c。此时Go对象为

Go {  
  a: undefined,
  b: function(){},
  // c: undefined,
  c: function(){},
}

六、接着第10行打印abc即第五步操作后的Go对象的属性。(a为undefined,b为函数,c为函数)

七、执行第11~14行的变量赋值操作,此时Go对象为

Go {  
  // a: undefined,
  // b: function(){},
  // c: function(){},
  a: 1,
  b: 2,
  c: 3,
}

八、最后执行第15行打印abc即第七步操作后的Go对象的属性。(a为1,b为2,c为3)

好啦!这下知道控制台打印的值都是为什么了吗。接下来我们看看局部预编译。

局部预编译 

 

 局部预编译发生在没一个函数体中,让我们来看看它的奥秘吧!

1 function func(a){        // 函数声明
 2    console.log(`1 -> a:${a} \n b:${b} \n c:${c} \n d:${d}`)
 3    var b                // 变量声明        
 4    function c(){}       // 函数声明方式
 5    var d = function(){} // 函数表达式方式
 6    console.log(`2 -> a:${a} \n b:${b} \n c:${c} \n d:${d}`)
 7    b = 2
 8    c = 3
 9    d = 4
10    console.log(`3 -> a:${a} \n b:${b} \n c:${c} \n d:${d}`)
11 }
12 func(1)                  // 函数调用

上面的代码在全局中声明了一个函数func并且调用,在函数内具有参数a、变量b、函数声明方式c、函数表达式方式d;并在各个阶段打印出abcd的值。

javascript前端 javaScript前端编译_赋值_02

 

我们来一步步来解释上面控制台所输出的结果。

一、首先这段代码会先经历全局预编译,和一次局部的函数预编译。经历全局预编译创建Go对象。

Go {   
  func: function(){}
}

二、func函数内部进行局部预编译。第一步创建局部Ao对象。

Go {   
  func: function(){}
}
Ao {
}

三、找func函数形参和变量声明,将变量和形参名作为AO对象属性名,属性值赋值为undefined;具有形参a、变量声明b、变量声明d。

Go {
  func: function(){}
}
Ao {
  a: undefined,
  b: undefined,
  d: undefined,
}

四、将实参值赋值给对应的形参属性。即将第12行函数调用所传入的实参1赋值给a属性。

Go {
  func: function(){}
}
Ao {
  // a: undefined,
  a: 1,
  b: undefined,
  d: undefined,
}

五、找函数声明,将函数名作为Ao属性名,值赋值为函数体;函数声明方式创建了c。

Go {
  func: function(){}
}
Ao {
  a: 1,
  b: undefined,
  c: function(){},
  d: undefined,
}

六、此时预编译已经完成,包括全局预编译与局部预编译。第1~11行为函数声明,执行第12行函数调用,然后开始执行函数内部代码,执行第2行,打印abcd即第五步操作后的Ao对象的属性(a为1,b为undefined,c为函数,d为undefined)

七、函数内部第3~5行,其中变量b与d的声明以及函数c的声明在局部预编译已经做过,只剩下第5行的变量d的赋值;此时的Ao对象为。

Go {
  func: function(){}
}
Ao {
  a: 1,
  b: undefined,
  c: function(){},
  // d: undefined,
  d: function(){},
}

八、执行函数内部第6行代码,打印abcd即第七步操作后的Ao对象的属性值。(a为1,b为undefined,c为函数,d为函数)

九、执行函数内部第7~9行的变量赋值操作,此时的Ao对象为。

Go {
  func: function(){}
}
Ao {
  a: 1,
  // b: undefined,
  // c: function(){},
  // d: function(){},
  b: 2,
  c: 3,
  d: 4,
}

十、最后执行函数内部的第10行,打印abcd的值即第九步操作后的Ao对象属性值。(a为1,b为2,c为3,d为4)