4.1 首先看现象
代码1:
a = 2;
var a;
console.log( a );//打印2
代码2:
console.log( a );//打印undefined
var a = 2;
上面这两个就是体现了JS中变量提升的简单示例,为什么是这样呢?
4.2 提升的本质
之前说过JS代码的执行会经过两个阶段,一个是编译阶段,一个是执行阶段。现有语句“var a = 2;“其实相当于“var a;”和“a = 2;”两条语句,其中第一个声明语句是在编译阶段进行的,第二个赋值语句是在执行阶段进行的,正因为这样所以声明语句的运行要先于赋值语句,所以就会有上面这种现象。
提升:由于声明所在的编译阶段要先于赋值所在的执行阶段,这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面,这个过程就叫做提升。
这就好像4.1中的代码2可以看成如下代码(提升是由于上述2个阶段引起的,通常我们都会把变量的提升理解为代码写在作用域的第一行,这样有助于我们分析代码,并且结果跟正确的结果是一致的):
var a;//声明相当于出现在作用域的第一行
console.log( a );//执行的时候还没有执行到赋值语句所以打印undefined
a = 2;
这样一来一切就明朗了。
4.3 函数的提升
函数和变量一样也会提升,这里会牵扯到一个顺序的问题,看如下代码:
foo(); // 这里打印3
function foo() {//这个foo是函数的声明会被提升,相当于放在了作用域的起始位置
console.log(1);
}
var foo = function() {//这个foo是变量(虽然赋值以后是函数),所以变量会被提升(但没有赋值),不过由于上面的foo已经提升了,也就是已经声明过了,这种情况这里的foo单纯的var声明将会被忽略。
console.log(2);
};
function foo() {//这个foo也会被提升,并且会覆盖之前提升的foo,所以打印3
console.log(3);
}
由上可知,对于同一个名称的提升,如果变量写在前面的话,那么函数的提升会覆盖变量的提升,如果函数写在前面的话,后面变量的提升(var声明这种情况)将会忽略,所以可以总结出函数的提升优先于变量的提升。
4.4 变量提升的坑
现在考虑一种比较特殊的提升,代码如下:
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
console.log(123);
};
上述代码中foo是TypeError应该不会有什么疑问的,由于foo是var声明的所以会提升,当执行的时候foo还没有赋值所以是undefined,所以直接以函数的形式调用是不行的,所以TypeError。对于bar这种情况,由于bar被赋值给foo,这种具名函数将会在函数外面被忽略,所以相对于函数外部来说bar相当于不写,故调用bar,会出现ReferenceError,这里说明一点,即使bar在最后一行调用也是不行的,因为bar在函数外部是忽略的。上述代码可以看做如下代码:
var foo;//foo提升了
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = foo;//只有在函数内部才能访问到bar
console.log(123);
};