在文章开始之前,先来看看下面的两端代码的运行结果会是怎样:

function a(){
    console.log('Hello');
}
a();

function a(){
    console.log('World');
}
a();

按照代码的书写顺序,打印出来的东西应该先是 'Hello',然后再是 'World',但结果并不是如此,两次函数调用打印的都是 'World'

image.png

那换成函数表达式结果又会不一样

var a = function(){
    console.log('Hello');
}
a();

var a = function(){
    console.log('World');
}
a();

这段代码的执行结果如下:

image.png

那到底是什么原因导致这两种函数的声明方式有这样的区别呢?

那是因为在函数执行之前,js引擎有一个预编译的过程。

预编译

预编译是在函数执行之前进行的,整个过程可以用四句话来描述:

  • 创建AO活动对象(Active Object);
  • 查找形参和变量声明,并赋予undefine;
  • 将实参和形参相统一;
  • 找函数声明,赋予变量指向函数的指针。

这里以下面这段代码来说明这个过程:

function test1(a, b) {
    console.log(a);  
    c = 0;
    var c;
    a = 3;
    b = 2;
    console.log(b); 
    function b() {}
    function d() {}
    console.log(b); 
}
test1(1);
  1. 创建AO活动对象:

    AO{
    
    }
  2. 查找形参和变量声明,并赋予undefine:

    AO{
     a: undefined,
     b: undefined,
     c: undefined
    }
  3. 将实参和形参相统一:

    AO{
     a: 1,
     b: undefined,
     c: undefined
    }
  4. 找函数声明,赋予变量指向函数的指针:

    AO{
     a: 1,
     b: function(){},
     c: undefined,
     d: function(){}
    }

那么函数声明式定义和函数表达式定义在这个过程中的区别到底在哪里呢?

两者的区别就在于赋予变量指向函数的指针的时机不一样。

函数声明式定义是在预编译的最后一步将指向函数的指针赋予给了变量;而函数表达式定义就相当于是定义一个变量,在代码的预编译阶段变量始终为 undefined,最后在执行上下文执行的时候再给变量赋予指向函数的指针。

从下面的这段代码我们可以看出,函数声明式定义在代码执行之前就已经给变量赋予了指向函数的指针,而函数表达式定义却还是 undefine

function a(){
    console.log('Hello');
}
console.log(b);
var b = function(){
    console.log('World');
}

image.png

所以最开始的两端代码会有所区别的原因就是:

  • 第一段代码,在预编译阶段就已经确定了变量指向的函数是哪一个函数,即第二次函数声明将第一次的覆盖掉了,所以变量始终指向第二个函数;
  • 第二段代码,在预编译阶段变量始终为 undefined,在函数执行时,再依次赋予变量指向函数的指针。
以上均为作者的个人理解,如有问题请各位多多指出!

参考资料:
理解JavaScript中的执行上下文
js预编译


习文
25 声望11 粉丝