变量提升
问题的产生
下面我们来看两个例子
1.
a = 2;
var a;
console.log(a)
这里你可能会认为输入的结果是undefined,因为 var a 声明在 a = 2 之后,会把之前的声明覆盖掉,所以结果是 undefined。但实际上打印的结果会是2
2.
console.log(a);
var a = 2;
鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码片段也会有同样的行为而输出 2。还有人可能会认为,由于变量 a 在使用前没有先进行声明,因此会抛出 ReferenceError 异常。
不幸的是两种猜测都是不对的。输出来的会是 undefined。
分析
分析之前需要了解的知识点——编译原理。
想要深入理解这个问题出现的原因,我们必须对编译的过程有所了解。
如果你曾经了解过其他编程语言,比如c,c++,在代码生成之前都需要在编辑器当中经历 编译 这一个步骤。在传统的编译语言中,程序中的一段源代码在执行之前需要经历三个步骤,统称为“编译”
1.词法分析:
这个过程会将由字符组成的字符串分解成(对于编程语言来说)有用的代码块,这些代码块被称为词法单元(token)。
2.语法分析
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。
3.代码生成
简单来说就是有某种方法可以将 var a = 2; 的 AST 转化为一组机器指令,用来创建一个叫作 a 的变量(包括分配内存等),并将一个值储存在 a 中。
根据编译原理继续分析
分析的正确思路:包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
举个例子,var a = 2;你可能会认识这是一个声明,是一个步骤,但是实际上会js将其看成两个声明过程,var a 和 a = 2;第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。
我们回看一开始的两个例子。
1.
var a;
a = 2;
console.log(a) //所以a为2
var a;
console.log(a);
a = 2;
其实可以这么理解:当我们遇见一个变量的声明时,会把他放(移动)到代码体的最上方,这个过程称之为“提升”。 需要注意 :提升的只有声明,而其他的辅助和逻辑并不会提升。
函数提升
函数提升的方式和变量提升有一些类似,函数提升会把函数的声明“移动到”代码体的最上方。
举个例子
1.函数声明的方式
fun1();// 可以被调用
function fun1 (){
console.log(a); // undefined
var a = 3;
}
2.函数表达式的方式
fun2();// 不是 ReferenceError, 而是 TypeError!
var fun2 = function bar(){
//...
}
执行的过程相当于
var fun2
fun2()
fun2 = function bar(){
//...
}