JavaScript的变量提升机制
变量提升
JavaScript的变量提升有两种,用var
声明的变量以及用function
声明的变量。
用var声明的变量
我们先来看下面这段代码,a的值是多少
代码1
console.log(a);
var a;
按照以往编程语言的思路来看,代码自上而下运行,按这种思路,会报错,因为执行到第2行时,变量a还没有定义,所以会报错a is not defined
然而事实上答案是undefined
好,抱着疑惑,我们看下面的代码
var a;
console.log(a);
我们发现,这两段代码是一样的,那么又有一个新的问题,是不是有没有var a
都无所谓,它的答案始终是undefined
,才造成了以为变量会提升的错觉,于是我写了代码3
代码3
console.log(a);
好,它终于报错了,所以这证明了javaScript
代码并不是自上而下执行的,至少从表面看上面是这样的。
于是我们再看代码4
代码4
console.log(a);
var a = 2;
因为变量提升嘛,所以答案是2,然而事实上,它依然是undefined
,why?
这时候我们有请编译器这位负责语法分析及代码生成等脏活累活的大佬。
编译器在看到var a = 2;
,它会将其看做两个声明,var a;
和a = 2
,第一个声明在编译阶段进行,第二个声明会被原地等待执行阶段。
也就是说上面代码,会变成下面的这段代码
var a;
console.log(a);
a = 2;
所以最终会是undefined
好,我在啰嗦一下,看这段代码5
代码5
a = 2;
var a;
console.log(a);
我想大家应该已经知道这段代码执行时的真正顺序及其答案了,没错,答案是2,但我想说的是把第2行给注释掉,答案依然是2,但这个和变量提升没啥关系了,是严格模式与非严格模式的锅,在非严格模式下允许开发者可以不使用声明变量的关键字,但在严格模式下是不可以的,它会报错的。
用function声明的变量
与var
一样,function
声明的变量依然会提升。
log(5);
function log(mes){
console.log(mes)
}
按照之前的变量提升的理解,这段代码的真正顺序是这样的,
function log(mes){
console.log(mes)
}
log(5);
很好,很正确,那么再看下一段代码
log(5);
var log = function(mes){
console.log(mes)
}
它报错了,log is not a function
,从这里我们可以看出,这种函数表达式是不会被提升的,只有函数声明才会被提升,试着在最前面新增一行代码console.log(log)
,会先输出undefined
。
所以这里的真正顺序是
var log;
log(); //这时候只是声明了log这个变量,并不是函数,却用函数的方法调用它,所以会报错,说这不是一个函数。
log = function(mes){
console.log(mes)
}
在function里用var声明变量
我们虽然知道,var
声明的变量会提升,但并不知道会提升到哪个程度。
在此之前来看一段代码
var a = 4;
function foo(){
var a = 5;
console.log(a);
}
foo();
console.log(a)
答案是5,4,先输出5,再输出4。
用var
声明的变量是有函数作用域的,所以foo里的a和foo外面的a没有任何关系,这种情况正是我想要的。
再改下代码
function foo(){
a = 5
console.log(a);
var a;
}
foo();
console.log(a)
答案是5,a is not defined
第4行代码输出5,第9行报错。
这种情况就是变量提升只会提升到变量所在的 作用域的顶部,不会提升到父级作用域。
因此可以得出一个结论:变量提升只会将变量提升到自己所在的作用域的顶部
函数优先
既然用var
和function
的变量都有提升的功能,那如果同一个变量用这两种都声明会怎样,好吧,看标题就知道了,函数优先。
具体看下代码
foo();
var foo;
function foo(){
console.log(1)
}
foo = function(){
console.log(2)
}
答案是1
这段代码其实这样子的
function foo(){
console.log(1)
}
foo();// 1
foo = function(){
console.log(2)
}
仔细一看,var foo;
没了,没错,它被引擎忽略了,认为重复声明所以把它抛弃了。
好,既然var
声明的变量比不了函数声明,那就用函数声明,多次声明同个变量。
foo()
function foo(){
console.log(1);
}
foo()
function foo(){
console.log(2);
}
foo()
function foo(){
console.log(3);
}
foo()
foo
声明了三次,调用了四次,每次调用的结果都是3,所以最后的函数声明会覆盖之前的函数声明
但是var
还想挣扎一下,觉得还是有必要证明自己的存在感的。
foo()
function foo(){
console.log(1);
}
var foo;
foo()
foo = function(){
console.log(2);
}
foo()
function foo(){
console.log(3);
}
foo()
仔细看,中间那部分代码改了,依次输出3,3,2,2
虽然var foo
被忽略了,但下面的函数还是有用的,这段代码可以看成是这样的
function foo(){
console.log(3);
}
foo();//3
foo();//3
foo = function(){
console.log(2);
}
foo();//2
foo();//2
在普通块内部声明函数
之前是在作用域声明函数,现在来块里面声明函数
function foo(){
console.log(b); // undefined
b(); //TypeError: b is not a function
var a = true;
if(a){
function b(){
console.log(2)
}
//下面这段代码和上面的结果一样
// var b = function(){
// console.log(2)
// }
}
//b() --> 这里会被执行
}
foo()
从上面看上去,b是undefined
,证明这个变量还是有的,只不过它并不是一个函数,这情况和用函数表达式差不多。
总结
- 提升分为函数声明提升和变量声明提升
- 声明变量用
var
,声明函数用function
- 变量提升会将变量提升到自己所在作用域的顶部
- 函数表达式不存在提升的机制。
- 函数声明和变量声明同时声明同一个标识符时,函数声明优先
- 多个函数声明同一个标识符时,最后一个声明覆盖先前的声明