一、先有蛋(声明)后有鸡(赋值)
直觉上会认为JS代码在执行时是由上到下一行一行执行的。但实际上这并不完全正确。下面看一段代码:
a=2;
var a;
console.log(a); //2
很多人觉得a声明在后,所以应该输出undefined,但事实并非如此,真正的输出结果是2。
再来看一段代码
console.log(a);
var a=2;//undefined
这时控制台会输出什么呢?你是不是会觉得这时应该输出2,然而这时的输出结果是undefined。
为什么会出现这种情况呢?这就涉及到JS的提升问题。
下面我们结合编辑器来理解一下。
引擎会在解释JavaScript代码之前首先对其进行编译。编译阶段的一部分工作就是找到所有的声明,并在合适的作用域将它们关联起来。
所以,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
var a=2;你可能会认为这是一个声明,但在JS中会将其看做两个声明:var a; 和a=2; 。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。
第一个代码段可以看做这种形式:
var a;
a=2;
console.log(a); //2
其中第一部分是编译,第二部分是执行。
第二个代码段可以看做这种形式:
var a;
console.log(a); //undefined
a=2;
这个过程就好像变量和函数声明的位置被“移动”到了最上面。这个过程就是提升。
可以理解为,先有蛋(声明)后有鸡(赋值)。
只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。
下面看一段代码:
foo();
function foo(){
console.log(a);//undefined
var a=2;
}
因为foo的函数声明被提升了,所以函数调用可以执行。
这段代码可看做:
function foo(){
var a;
console.log(a);//undefined
a=2;
}
foo();
注意:函数声明会提升,但函数表达式并不会提升!
foo(); //Uncaught TypeError: foo is not a function
var foo= function bar(){
//...
}
这段代码中foo()被提升并分配给所在作用域,因此foo()不会导致ReferenceError。但此时foo并未赋值,foo()由于对undefined值进行函数调用而导致非法操作,因此抛出TypeError异常。
二、函数优先
函数声明和变量声明都会被提升。但要注意:函数首先会被提升,其次才是变量。
看下面一段代码:
foo(); //1
var foo;
function foo(){
console.log(1);
}
foo=function(){
console.log(2);
}
此时会输出1。
可看做
function foo(){
console.log(1);
}
foo();
foo=function(){
console.log(2);
} //var foo;被忽略
这个时候var foo;声明会被忽略,因为它是重复的声明。(函数声明会被提升到普通变量之前)
看这段代码:
foo(); //a
function foo(){
console.log("a");
}
var foo=function foo(){
console.log("b");
}
foo(); //b
等价于
function foo(){
console.log("a");
}
foo(); //a
//var foo; //被忽略
foo=function foo(){
console.log("b");
}
foo(); //b
此时var声明被忽略,但出现在后面的函数声明可以覆盖前面的。
foo(); //c
function foo(){
console.log("a");
}
var foo=function foo(){
console.log("b");
}
function foo(){
console.log("c");
}
最后切记:声明本身会被提升,而包括函数表达式的赋值在内的赋值操作不会被提升!
知识来源:《你不知道的JavaScript》