为什么 let 有变量提升?

253 阅读2分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

为什么 let 有变量提升?搞懂这个问题之前我们要知道什么是变量提升。

什么是变量提升(hoisting

变量提升是 JavaScript 引擎在执行一段代码时做的预处理工作,预处理会把变量的声明放到函数或全局的顶部,这样能保证变量在使用的时候都声明过了。

console.log(a)
var a = 1

//上面代码经过变量提升后会处理成,注意这里是引擎内部的效果,不会修改代码

var a
console.log(a)
a = 1

let 声明的变量和 var 声明的变量的区别

var 声明变量

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

上面例子中最后会输出 2,foo 中的 a = 1 并没有修改到函数外面的变量 a,说明这里的 a 是后面用 var a 声明的 a。

let 声明变量

var a = 2
function foo() {
    try {
        a = 1  //Uncaught ReferenceError: Cannot access 'a' before initialization
        return
        let a
    } catch(e){}
}
foo()
console.log(a) //2

上面例子中最后会输出 2,foo 中的 a = 1 抛错了,说明这里的 a 是后面用 let a 声明的 a。

所以 let 声明的变量也有提升,只是表现的方式和 var 不一样,如果在声明前使用会抛错。

let 变量提升带来的暂时性死区

function foo() {
    console.log(a)
    let a = 1
}

//预处理后

function foo() {
    let a
    //---------------变量a的暂时性死区开始---------------
    console.log(a)
    //---------------变量a的暂时性死区结束---------------
    a = 1
}

与通过 var 声明的有初始化值 undefined 的变量不同,通过 let 声明的变量直到它们的定义被执行时才初始化。在变量初始化前访问该变量会导致 ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中。

除了变量提升还有哪些提升?

除了变量会提升外,函数和类在预处理时也会提升。

Function 提升

foo('varlet')

function foo(name) {
    console.log(`Hello ${name}.`) //Hello varlet.
}

Function 通过提升后,可以在任意位置调用。

Class 提升

var foo = new Foo('varlet')
foo.sayHello()

class Foo {
    constructor(name) {
        this.name = name
    }
    
    sayHello() {
        console.log(`Hello ${this.name}.`) //Hello varlet.
    }
}

Class 通过提升后不能在声明前使用,因为类没有使用默认值进行初始化,所以如果在声明前使用了类会抛 ReferenceError 错误。

变量提升带来的方便和不便

变量提升能让我们更方便得写代码,譬如调用函数时不必担心函数是否已经声明。但是也带来了坏处,譬如经典的循环陷阱

function foo() {
    var arr = new Array(10)
    for (var i = 0; i < 10; i++) {
        arr[i] = function() {
            console.log(i)
        }
    }
    
    return arr
}

var arr = foo()
arr[0]()  //10
arr[1]()  //10
arr[2]()  //10

在 ES6 中我们可以使用 let 代替 for 循环中的 var 来声明变量,把 i 的作用域变成块级作用域,从而避免循环陷阱问题。

如有错误欢迎指出,欢迎一起讨论!