JavaScript之变量提升

前言

小的时候总是纠结于先有蛋还是先有鸡这个问题,虽然至今仍旧没有答案,但是大家还是津津乐道于这个问题之中。JavaScript中的变量提升类似于这个问题,不同之处在于你可以获取肯定的答案 O(∩_∩)O~~

变量提升

首先我们先来看两段小代码,猜测一下其输出值是什么:

1
2
3
a = 9;
var a;
console.log(a);
1
2
console.log(a);
var a = 9;

两段代码的输出值是9 ,是 undefined ,还是 ReferenceError 呢?如果你在控制台测试一下你会发现第一段代码的输出值为 9 ,第二段代码的输出值为 undefined 。如果大家不明白JavaScript中的提升机制肯定会迷惑于此,接下来我们就慢慢深入其中。

原理分析

对于var a = 9; ,JavaScript会将其看成是两个声明: var a;a = 9;。第一个定义声明是在 编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。上面的两段代码会以如下形式进行处理:

1
2
3
var a;
a = 9;
console.log(a); //9
1
2
3
var a;
console.log(a); //undefied
a = 9;

这个过程就像是变量和函数声明从原来的位置移动到了当前作用域的最顶部,这个过程就叫做 提升。只有声明本身会被提升,而赋值操作会留在原地。需要注意的是: 每一个作用域都会进行提升操作。

函数提升
函数声明

函数声明会被提升,如下:

1
2
3
4
function foo(){
console.log(a);
var a = 9;
}

实际代码的执行形式如下:

1
2
3
4
5
function foo(){
var a;
console.log(a); // undefined
a = 9;
}

在函数foo中进行了提升操作,变量a被提升到了foo函数作用域的顶部,所以其输出值为undefied。这里函数声明foo也会被提升到全局作用域的顶部。

函数表达式

函数表达式不会被提升:

1
2
3
4
foo();
var foo = function bar(){
...
}

实际代码的执行形式如下:

1
2
3
4
5
var foo;
foo(); // TypeError
foo = function(){
...
}

这里引发TypeError异常,原因在于foo没有被赋值,值为undefined,对其进行函数调用会导致非法操作抛出异常。

函数优先

函数会首先被提升,然后才是变量。

1
2
3
4
5
6
7
8
9
foo();
var foo;

function foo(){
console.log(9);
}
foo = function(){
console.log(19);
}

实际代码的执行形式如下:

1
2
3
4
5
6
7
8
9
function foo(){
console.log(9);
}

foo(); // 9

foo = function(){
console.log(19);
}

函数声明会被提升到普通变量之前,所以上述代码输出值为 9,而不是19

这里还需要注意一点,出现在后面的函数声明会覆盖前面的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
foo();   // 39

function foo(){
console.log(9);
}

var foo = function(){
console.log(19);
}

function foo(){
console.log(39);
}

这里foo()输出值为39而不是29

let 和 const 不存在提升现象

尽管变量声明和函数声明存在提升现象,但是ES6中letconst声明不具有提升现象,其存在一个 暂时性死区。如下:

1
2
3
4
5
6
7
8
9
10
console.log(a);  // undefied
console.log(b); // ReferenceError

var a = 9;
let b = 19;

{
console.log(c); // ReferenceError
const c = 39;
}
总结
  • 每一个作用域都会进行提升操作。
  • 变量声明和函数声明会被提升,但是函数表达式不会被提升。
  • 函数声明提升优于变量声明提升。
  • ES6中的letconst不具有提升现象。