# 第 4 章 提升
# 4.1 先有鸡还是先有蛋
直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的。但实际上这并不完全 正确,有一种特殊情况会导致这个假设是错误的。
a = 2;
var a;
console.log(a); // 2
console.log(a); // undefined
var a = 2;
看起来我们面对的是一个先有鸡还是先有蛋的问题。到底是声明(蛋)在前,还是赋值(鸡)在前?
# 4.2 编译器再度来袭
为了搞明白这个问题,我们需要回顾一下第 1 章中关于编译器的内容。回忆一下,引擎会在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
代码片段
foo();
function foo() {
console.log(a);
var a = 2;
}
编译后
function foo() {
// 第一次提升 foo
var a; // 第二次提升 var
console.log(a);
a = 2;
}
foo();
执行
打个比方,这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面。这个过程就叫作提升。
换句话说,先有蛋(声明)后有鸡(赋值)。
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
// 函数表达式
// ...
};
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...self...
// ...
}
# 4.3 函数优先
函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个 “重复”声明的代码中)是函数会首先被提升,然后才是变量。
foo();
var foo;
function foo() {
console.log(1);
}
foo = function () {
console.log(2);
};
foo();
function foo() {
console.log(1);
}
foo(); // 1
// var foo;
foo = function () {
console.log(2);
};
foo(); // 2
# 4.4 小结
我们习惯将 var a = 2; 看作一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a 和 a = 2 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。
这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升