let到底会不会造成变量提升(Hoisting)?

2,394 阅读6分钟

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

变量提升(Hoisting)

我们写这样写JavaScript代码是不会报错的,变量会被提升至当前作用域的顶部。

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

image.png

之所以不会报错,是因为JavaScript在运行时会把用var命令声明的变量a,提升(Hoisting)到当前作用域的顶部。等价于

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

image.png

上面代码中,变量avar命令声明时,会变量提示(Hoisting)了,那么使用let会怎样呢?

console.log(a) // ReferenceError: a is not defined
let a = 1

image.png

此时我们量alet命令声明,被抛出错误ReferenceError: a is not defined。此时从现象上来看好像是没有造成变量提升(Hoisting)。那么真实情况到底是怎样的呢?如果let不会造成变量提升(Hoisting),我为什么会被大佬嘲笑呢?

image.png

从红宝书里寻找答案

下面我们试着从红宝书(JavaScript高级程序设计)里找找答案。

var 声明提升

在红宝书中的第3.3 变量中是这样介绍var 声明提升的 image.png

大致来看与我所说的一样,不过它这里介绍了用 var 声明同一个变量的时候,也和变量提升(Hoisting)有关。

var a = 0
var a = 1
var a = 2
console.log(a) // 2

image.png

在JavaScript运行时的会等价于以下代码

var a = 0
a = 1
a = 2
console.log(a) // 2

let 暂时性死区 (temporal dead zone)

在红宝书中的第3.3 变量中是这样介绍 let 中提到了一个新的概念暂时性死区(temporal dead zone)。

image.png

在let使用声明变量,JavaScript 运行时也是会注意到后面的 let 声明,但是不能被引用。这写就是暂时性死区 (temporal dead zone)了。此时我们使用未声明的变量会抛出 ReferenceError: xxx is not defined

image.png

但在红宝书第4 4.2.2 变量声明却说严格的说let 在 JavaScript 运行时中也会被提升。难道红宝书出现错误导致说法不一样?

image.png

通过仔细观察会发现俩个说法没有存在冲突:

  1. let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升.
  2. 严格来讲,let 在 JavaScript 运行时中也会被提升,但由于“暂时性死区”(temporal dead zone)的缘故,实际上不能在声明之前使用 let 变量。

此时总结一下可能就是,let 不会直接被提升到当前作用域顶部,由于“暂时性死区”(temporal dead zone)的缘故,不能在声明之前使用 let 声明的变量。

进阶一下

let

通过上面的对红宝书的阅读与分析,大致了解 let 会造成 变量提升(Hoisting)但是与 var的提升不太一样。由于“暂时性死区”(temporal dead zone)的缘故,是不能被使用的。但是还是有许多问题不清楚,比如暂时性死区(temporal dead zone)是如何出现的。这个问题可能只能从 ecma262 中找到答案了。

ecma262的14.3.1 Let and Const Declarations中有这些句话:

let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment.

意译成中文:通过 letconst 声明定义了作用域的变量到正在运行的执行上下文(context)的词法环境(LexicalEnvironment)

The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.

意译成中文:变量在实例化的时候通过词法环境(LexicalEnvironment)完成创建但由于还为进行词法绑定,还不能被访问。

If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated。

意译成中文:如果词法绑定未在初始化时为赋值的,则在词法绑定(LexicalBinding)时,为变量分配 undefined 的值。

这几段话大致是在说,在JavaScript 运行时中,let 声明的变量。会在词法环境(LexicalEnvironment)首先完成创建过程,此时不能被访问。只有完成初始化赋值并进行词法绑定(LexicalBinding)才能够被访问。初始化没有赋值则会默认赋值成undefined

模拟初始化时没有赋值的情况

let a
console.log(a) // undefined

模拟初始化赋值的情况

let a = 1
console.log(a) // 1

这里虽然理解了在未完成初始化的时候不能被访问,但是没有说为啥会抛出错误?但是提到过词法环境(LexicalEnvironment)那么抛出错误应该会在词法环境中进行展开说明了

ecma262的 9.1.1 The Environment Record Type Hierarchy中有这些句话:

If the binding exists but is uninitialized a ReferenceError is thrown, regardless of the value of S.

意译成中文:如果绑定应该没有完成初始化的值会抛出 ReferenceError 错误。

整体流程如下:

image.png

var

ecma262的14.3.2 Variable Statement中有这些句话:

Var variables are created when their containing Environment Record is instantiated and are initialized to undefined when created

意译成中文:var声明的变量会在词法环境(LexicalEnvironment)首先完成初始化创建过程,在初始化时如果没有赋值则默认赋值为undefined

简单来说就是 var的创建阶段与初始化阶段是同时完成的直接进入赋值阶段

整体流程如下:

image.png

const

ecma262的14.3.1.1 Static Semantics: Early Errors中有这些句话:

It is a Syntax Error if Initializer is not present and IsConstantDeclaration of the LexicalDeclaration containing this LexicalBinding is true.

意译成中文:如果一个常量在绑定阶段没有被赋值那么这就一个语法错误

image.png

ecma262的14.3.1.2 Runtime Semantics: Evaluation 中有这些句话:

A static semantics rule ensures that this form of LexicalBinding never occurs in a const declaration..

意译成中文:静态语义规则确保const 声明 不能出现重复绑定赋值

总结

let到底会不会造成变量提升(Hoisting)在使用的时候是报错了 。按照红宝书中所说结合 ecma262 严格来讲 let 的提升只是提升了创建阶段,此时还不能被访问。如果冒然的访问会抛出错误 ReferenceError。而 var 的提升由于var的创建阶段与初始化阶段合二为一了直接进入赋值阶段可是访问的。

番外

ecma262的中14.3.1.1 Static Semantics: Early Errors有这些句话:

It is a Syntax Error if the BoundNames of BindingList contains "let".

意译成中文:在绑定列表中的变量如果存在 let会报错

let let = 1 // Uncaught SyntaxError: let is disallowed as a lexically bound name

image.png

但是换成 var 或者 const也是会报错,但报错内容不一样

let var = 1 // Uncaught SyntaxError: Unexpected token 'var'

image.png

到此这个分析就结束了,看的头晕晕的,不说去睡觉了。睡前还要麻烦各位老哥动动小手点点三联。当然也欢迎各位老哥在下方留言说出自己的看法。