Skip to content

提升

作用域同其中的变量声明出现的位置有某种微妙的联系。

直觉上会认为JavaScript的代码会一行一行往下执行。但实际上这并不完全正确。

考虑以下代码:

js
a = 2
var a
console.log(a) // 2

为什么输出结果不是undefined?

js
console.log(a) // undefined
var a = 2

为什么没有出现ReferenceError错误?

造成以上代码的原因就是我们现在要探讨的提升。

编译器再度来袭

回想一下作用域中关于编译器的内容。引擎会在解释JavaScript代码之前首先对其编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来,也就是词法作用域

因此,正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

第一个代码片段会以如下形式处理:

js
var a
a = 2
console.log(a) // 2

第二个代码片段会以如下形式处理:

js
var a
console.log(a) // undefined
a = 2

这个过程就好像,变量和函数的声明从它们在代码编写的位置被“移动”到了最上面,这个过程就叫做提升

值得注意的是,每个作用域都会进行提升操作。

js
foo()

function foo() {
  console.log(a) // undefined
  var a = 2
}

以上代码片段会以如下形式处理

js
function foo() {
  var a 
  console.log(a) // undefined
  a = 2
}

foo()

函数提升

函数声明会被提升,而函数表达式不会被提升。

js
foo() // 不是ReferenceError 而是TypeError!
var foo = function bar() {
  // ...
}

在这段代码中变量的标识符foo()会被提升并分配给所在作用域(在这里是全局作用域),因此代码中foo()不会导致ReferenceError。但是foo此时没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。所以此时的foo() 相当于是undefined(),所以会出现TypeError。

函数优先

函数和变量声明都会提升。函数会首先被提升,然后才是变量。

考虑如下代码:

js
foo() // 1

var foo

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

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

以上代码片段会以如下形式处理

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

var foo // 重复声明会被忽略

foo() // 1

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

尽管重复的var声明会被忽略,当时出现在后面的函数声明还是可以覆盖前面的。

js
foo() // 3

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

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

function foo() {
  console.log(3)
}

我们都习惯想var a = 2看成是一个声明,但实际上JavaScript引擎并不这么认为。它将var aa = 2当作两个单独的声明,第一个是在编译阶段的任务,第二个是在执行阶段的任务。