提升


示例

分别在 script 元素内和控制台内编写以下代码并执行:

console.log(foo);
var foo = 1;

如果在控制台中,因为 foo 还没有被声明,所以对 foo 变量取值打印的时候程序会报错,提示 foo 未定义。然而在 script 元素内编写上述代码并执行时,代码可以顺利执行,foo 被打印出了 undefined。发生这种情况是因为 JS 代码的运行方式。

先编译再解释

JS 的代码运行过程分为两个阶段,编译和解释,代码会在 编译后再解释,变量和函数的声明以及确定作用域的过程都是发生在编译阶段。

如果把一个完整的声明语句分为两部分:

var bar = 1;

拆分为:

var bar;
bar = 1;

声明部分的代码会在编译阶段进行,而其他部分的代码会在解释阶段执行。这种运行方式与变量声明和函数声明所在的位置无关。比如:

console.log(foo);
bar();

var foo = 1;
function bar() {
  console.log("bar function");
}

上面的代码会按照一下方式运行:

// 所有声明在编译时处理,所以先执行,这里可以理解为编译阶段
var foo;
function bar() {
  console.log("bar function");
}

// 然后按照顺序小执行后续代码,这里是解释阶段
console.log(foo);
bar();

foo = 1;

所以在 JS 中,声明像是被放到了代码开头执行,这个过程被叫做 提升,不论变量还是函数声明,都会产生提升。

函数优先

在编译阶段时,变量声明在前,函数声明在后。所以当出现变量声明与函数声明同名时,会产生函数声明覆盖,同名的变量会函数声明覆盖:

比如以下代码:

foo(); // 1

// 这是函数表达式,不会提升,只是声明会提升
foo = function () {
  console.log(2);
};

foo(); // 2

function foo() {
  console.log(1);
}

var foo;

实际执行过程应该如下:

// 编译阶段,变量声明在前,函数声明在后
var foo;
// 函数声明产生覆盖,所以函数优先
function foo() {
  console.log(1);
}

// 以下是解释阶段,顺序执行

foo(); // 1

foo = function () {
  console.log(2);
};

foo(); // 2

隐式声明不会提升

因为隐式声明没有 var 关键字,引擎无法在编译阶段识别出隐式声明,所以隐式声明不会产生提升。隐式声明发生在解释阶段:

console.log(foo); // error
foo = 0;

上诉代码会发生错误,因为对 foo 取值时,还没有声明。

函数内部也会先编译再执行

函数执行时,也会先编译再解释,所以函数内部也会发生提升:

function foo() {
  console.log(bar);
  var bar;
}
foo();

上面代码中,bar 的声明也会被提前,所以打印 bar 不会报错,是默认值 undefined

总结

  • var bar = 1 会被拆分为 var bar;bar = 1 分别执行,声明会在编辑阶段生效,赋值会在解释阶段有效。
  • var 声明和函数声明在 JS 执行中被率先处理,这个过程被叫做提升。
  • 在提升中,函数优先。
  • 如果你觉得本节内容较难,你可以这样阅读代码,把同一作用域内的所有变量声明和函数声明放到程序的最前,并函数放到变量后面,这样就是程序执行的顺序。