首先,我们来看一个小问题
a=1;
var a;
console.log(a);
大家看一下,以上的代码片段会输出什么呢?可能有部分人认为是defined,他们觉得var a是在a =1之后声明,所以a会被重新赋值为undefined。不过,不幸的消息是它的输出结果是1。
我们继续看下一段代码:
console.log(a);
var a = 1;
大家看到这,因为第一段代码的问题,可能会有很多人觉得现在会输出1,也有部分人觉得a在使用之前没有声明,会抛出一个ReferenceError异常,其实不然,它在这里的输出结果是undefined
这里就涉及到一些关于JavaScript的变量提升的问题,为了更好的理解变量提升,我们先来看一下JS的编译器设计规范。
变量怎么提升?
大家都已经了解,JS是一门动态语言,他需要经过编译器编译之后再被引擎解析才能执行操作。编译过程中的一项工作就是找出所有的声明,并且将他们与合适的作用域匹配。
变量和函数的所有声明会在任何代码被执行之前首先被编译器处理
如对于var a = 1,有些语法书或者通常我们会觉得这就是一个声明。其实,在JS编译器中,他是分为var a;和a=1两段进行处理的,var a;会在编译阶段执行,a=1则在代码执行到这里才会执行。
变量的提升,只有声明本身得到了提升,如var a;得到提升,而赋值和其他操作则会先停留在原地,在代码执行到这一行才会执行。
所以,我们再回到第一段代码,它可以看成与下面这一段代码一样:
var a;
a = 1;
console.log(a);
第二段代码为:
var a;
console.log(a);
a = 1;
在执行第二段代码的console.log(a)时,a还没有被赋值,所以输出结果为undefined。
注意,这个提升是指在它的作用域中进行提升,一个作用域里的提升并不会提升到程序(全局)的最上方。比如说你的变量是在一个函数作用域里面,那这个变量只会提升到函数作用域最上方,并不会变成全局变量
此外,函数声明也是会被提升的,如下:
foo();
function foo(){
// code
}
但函数的表达式不会被提升,如下:
foo(); //抛出TypeError异常
var foo = function(){
// code
}
因为var foo被提升并分配给所在作用域,所以它不会抛出ReferenceError异常,但是foo后半部分的赋值操作并没有得到提升,所以相当于foo此时是undefined,对undefined进行函数调用是非法操作,于是抛出TypeError异常。
函数优先原则
我们先看下面这段代码:
foo();
var foo;
function foo(){
console.log(1);
}
foo = function(){
console.log(2);
}
foo()会输出1,因为函数function foo(){}会比var foo优先提升,这里要注意。所以,上面的代码可以看成如下样子:
function foo(){
console.log(1);
}
foo();
// var foo; 重复声明foo会被忽略
foo = function(){
console.log(2);
}
由于函数优先,所以在这里var foo是重复声明,会直接被忽略。但是后出现的函数声明可以覆盖从前的函数声明:
foo(); //3
function foo(){
console.log(1);
}
var foo = function(){
console.log(2);
}
function foo(){
console.log(3);
}
所以,一般禁止在同一个作用域进行重复定义,因为会出现各种各样的Bug!!!