Skip to content
JavaScript 的提升
创建于 2022-04-07

最近在回顾 JavaScript 的基础。

于是碰到了 提升 这个概念

关于,提升,一直有印象的,就是 var 这个变量提升。

提示

每个作用域都会有“提升”操作,提升操作会将变量提升到作用域的顶部。

var 的提升

使用 var 声明的变量,会在所有的作用域中提升。

  1. 基本

    JavaScript
    var a = 'a001'
    console.log(a) // a001
  2. 先打印,接着声明,赋值

    JavaScript
    console.log(b) // undefined. ReferenceError: b is not defined
    var b = 'b001'

    在编译器中等价于

    JavaScript
    var b
    console.log(b) // undefined. ReferenceError: b is not defined
    b = 'b001'
  3. 重复声明并赋值

    JavaScript
    var c = 'c001'
    var c = 'c002'
    console.log(c) // c002

    但是你假如对使用 let/const 声明的变量进行了重复声明,那么就会报错。

    JavaScript
    let c1 = 'c1001'
    let c1 = 'c1002' // SyntaxError: Identifier 'c1' has already been declared
    console.log(c1)
    
    const c2 = 'c2001'
    const c2 = 'c2002' // SyntaxError: Identifier 'c2' has already been declared
    console.log(c2)
  4. 先“赋值”,再声明

    JavaScript
    d = 'd001'
    var d
    console.log(d) // d001
  5. 仅“赋值”,就会变成 window 上的变量

    JavaScript
    e = 'e001'
    console.log(e) // e001。若后续同一作用域下代码无 `var e`,那这 `e` 便是 window.e 了。
    
    // or
    function f() {
      e = 'e002'
    }
    f()
    console.log(e) // e002。跟上面的 `e`,是相同的情况
    
    // but
    if (false) {
      e = 'e003'
    }
    console.log(e) // undefined。跟上面的两个 `e`,是不同的情况。因为不存在变量提升,代码也没有执行 if 块里的
  6. 在函数中

    JavaScript
    function g() {
      var h = 'h001'
      console.log('in function', h) // h001
    }
    g()
    console.log('in window', h) // undefined。因为函数内部的变量,不会提升到作用域的顶部,因为该变量会在调用函数之后随即被销毁掉。

函数的提升

或许是好久没看概念,这两天看书发现:

函数里也是有“提升”的。

但仅限于函数声明会有提升,而函数表达式是不会提升的。

《JavaScript 高级程序设计(第 4 版)》P297

函数声明会在任何代码执行之前先被读取并添加到执行上下文。这个过程叫作函数声明提升(function declaration hoisting)。在执行代码时,JavaScript 引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。因此即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部。

具体表现为

JavaScript
// 没问题
console.log(sum(10, 10)) // 20
function sum(num1, num2) {
  return num1 + num2
}

但是把上述的函数声明改为等价的函数表达式,那么执行的时候就会出错。

JavaScript
console.log(sum(10, 10)) // Uncaught ReferenceError: sum is not defined
let sum = function (num1, num2) {
  return num1 + num2
}

哪怕是你换成用 var 声明的函数,也会出错。

JavaScript
console.log(sum(10, 10)) // Uncaught ReferenceError: sum is not defined
var sum = function (num1, num2) {
  return num1 + num2
}

除了“函数什么时候真正有定义”这个区别之外,这两种写法是等价的。

提升的优先级

重复的声明,将被忽略掉

先看点简单的例子

JavaScript
foo()
var foo
function foo() {
  console.log(1)
}
foo = function () {
  console.log(2)
}

上述代码执行的结果是:1

在编译器中,被理解为:

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

注意

foo() 这里的 foo 只是一个函数名,不是函数声明。函数声明是包含函数名的。在此也可以理解为是个变量,函数变量。跟之后的 var foo 中的 foo 是相同的,同名变量。所以,后面的 var foo 将被忽略掉。因为是重复的声明

函数声明会被提升到变量声明之前

再来看看这个例子

JavaScript
console.log('a -1', a) // a -1 f a()
console.log('a() -2', a()) // 这里是先执行 a(),后打印返回值
var a = 1
function a() {
  console.log('函数 a', 1)
  return `函数 a 2`
}
console.log('a -8', a) // a -8 1
a = 3
console.log('a() -10', a()) // a not a function,该错误会阻断后续的代码执行
console.log('a -11', a)

重复的声明,将被忽略掉

变量的值,只会在重新赋值后才会被改写。而重复的声明,会被忽略,并不会影响什么。

援引自 tc39/ecma262 中的阐述

A variable defined by a VariableDeclaration with an Initializer is assigned the value of its Initializer's AssignmentExpression when the VariableDeclaration is executed, not when the variable is created. source

  1. 变量的第一次声明,会被默认赋值为 undefined。假如接着赋值,那么就会覆盖掉默认值。

var let

只声明,不赋值,默认值为 undefined

除非是显式的赋值,否则变量的值,在非第一次执行变量声明时,是不会被赋值的。是无效的,会被编译器忽略掉。

const

必须声明并赋值(初始化),否则报错。

只声明,不赋值。是无效的声明。

只声明,或再打印它:const 声明中缺少初始化器。

JavaScript
const b // SyntaxError: Missing initializer in const declaration
// or
const b
console.log(b) // SyntaxError: Missing initializer in const declaration

当使用这个变量去赋值给其他变量的时候,会提示未定义

JavaScript
const b
const c = b // ReferenceError: b is not defined

在 JavaScript 引擎里,

JavaScript
var a = 2

被看作两个单独的语句

JavaScript
var a // 编译阶段的任务
a = 2 // 执行阶段的任务

《你不知道的 JavaScript(上卷)》4.4 小结

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

声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。

要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,否则会引起很多危险的问题!

最后更新于: