我们在直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的。但实际上这并不完全正确,有一种特殊情况会导致这个假设是错误的。
考虑以下代码:
a = 2;
var a;
console.log( a );
你认为 console.log(..) 声明会输出什么呢? 很多人会认为是 undefined,因为 var a 声明在 a = 2 之后,他们自然而然地认为变量被重新赋值了,因此会被赋予默认值undefined。但是,真正的输出结果是 2。
考虑另外一段代码:
console.log( a );
var a = 2;
鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码片 段也会有同样的行为而输出 2。还有人可能会认为,由于变量 a 在使用前没有先进行声明, 因此会抛出 ReferenceError 异常。
不幸的是两种猜测都是不对的。输出来的会是 undefined。
编译器
引擎会在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
var a = 2;这条代码JavaScript会将其看成两个声明:var a;和a = 2;前者是在编译阶段进行的。后者赋值声明会被留在原地等待执行阶段。
我们的第一个代码片段会以如下形式进行处理:
var a; //编译
a = 2; //执行
console.log( a );
类似地,我们的第二个代码片段实际是按照以下流程处理的:
var a;
console.log( a );
a = 2;
这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动” 到了最上面。这个过程就叫作提升。
先声明后赋值!!
不只是变量会被提升,函数也会被提升,函数内部的变量也会被提升。
函数提升以及函数内部变量提升示例代码:
foo();
function foo() {
console.log( a ); // undefined
var a = 2;
}
上述代码提升后的执行顺序
function foo() {
var a;
console.log( a ); // undefined
a = 2;
}
foo();
函数优先
函数声明和变量声明都会被提升。但是函数会首先被提升,然后才是变量。
示例代码:
foo(); // 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
会输出 1 而不是 2 !这个代码片段会被引擎理解为如下形式:
function foo() {
console.log( 1 );
}
foo(); // 1
foo = function() {
console.log( 2 );
};
var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽 略了),因为函数声明会被提升到普通变量之前。
尽管重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。如下代码示例:
foo(); // 3
function foo() {
console.log( 1 );
}
var foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
}
后面的3会覆盖前面的1。
在JavaScript中,var foo(..)只会提升foo变量名,并不会提升foo后面的(..)里面的内容。而function foo()则会将foo变量名和(..)里内容一起提升,所以function foo()里的内容会比var foo先执行,而后面的function foo()也会覆盖前面的同名函数从而导致此片段代码最后的输出结果是3。