JavaScript的变量提升

科海拾零 / 2023-08-27 / 原文

参考资料:https://time.geekbang.org/column/article/126339

目录
  • 变量提升
  • 变量形式声明的函数
  • 变量提升导致的问题
    • 变量被覆盖
    • 变量不被销毁
  • 避开变量提升
    • 引入letconst关键字
    • 块级作用域

变量提升是在代码执行时,把变量和函数的声明部分提升到代码开头的行为,变量被提升后,会被默认设置为undefined

变量提升

当使用var关键字声明变量时,

consolt.log(a)
var a = 1
// undefined

等价于

var a
console.log(a)
a = 1

变量a作为全局变量时,其声明会被提升到全局作用域的顶端,而初始化和赋值不会。

在函数作用域中

function main() {
    console.log(a)
    var a = 1
}

main()
// undefined

函数内部变量的声明会被提升到函数作用域的顶端,等价于

function main() {
    var a
    console.log(a)
    a = 1
}

main()

变量形式声明的函数

JavaScript中函数的声明有两种形式

function main() {
    // 函数形式声明
}

var func = main() {
    // 变量形式声明
}

在使用变量形式声明函数时,也会出现提升的现象。

func()

var func = function () {
    console.log("Hello World!\n")
}
// TypeError: func is not a function

func1()
function func1() {
    console.log("Hello World!\n")
}
// Hello World!

变量提升导致的问题

变量被覆盖

var s = "HTML"
function main() {
    console.log(s);
    if (0) {
        var s = "CSS"
    }
}

main()
// undefined

当执行到console.log(s)时,会在执行上下文中寻找变量s,一个在全局上下文中HTML,另一个在函数main的执行上下文中CSS

JavaScirpt会优先从当前的执行上下文(函数的执行上下文)中查找变量,由于变量提升的存在,包含了s = undefined

变量不被销毁

function main() {
    for (var i = 0; i < 6; ++i) {

    }
    console.log(i)
}

main()
// 6

避开变量提升

引入letconst关键字

ECMAScript6已经通过引入块级作用域和let, const关键字避开这种设计缺陷。

console.log(a)
let a = 1
// ReferenceError: Cannot access 'a' before initialization
function main() {
    let a = 1
    if (1) {
        let a = 2
        console.log(a)
    }
    console.log(a)
}

main()
// 2 1

let关键字支持块级作用域,作用块内的变量不影响外面的变量。

块级作用域

function main() {
    var a = 1
    let b = 2
    {
        let b = 3
        var c = 4
        let d = 5
        console.log(a)
        console.log(b)
        console.log(d)
    }
    console.log(b)
    console.log(c)
}

main()
//  1 3 5 2 4

在执行代码前,JavaScript会先编译并创建执行上下文

  • var声明的变量,会被存到变量环境
  • let声明的变量,会被存到词法环境
  • 函数内部let声明的变量不会存到词法环境

执行到main函数时,变量环境中a = 1,词法环境中b = 2,内部的块作用域中用let声明的变量,存到词法环境中的单独区域。

在词法环境中,维护一个栈结构,栈底是最外层变量,进入一个作用域后,把内部的变量压到栈顶,执行完后在出栈。

执行到console.log()时,从词法环境的栈顶向下查找,如果没有找到,继续在变量环境中查找:

块作用域执行结束后,内部变量的定义出栈,继续向下执行其余代码。