# 第 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 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升