let到底会不会造成变量提升(Hoisting)?
「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
变量提升(Hoisting)
我们写这样写JavaScript代码是不会报错的,变量会被提升至当前作用域的顶部。
console.log(a) // undefined
var a = 1
之所以不会报错,是因为JavaScript在运行时会把用var
命令声明的变量a
,提升(Hoisting)到当前作用域的顶部。等价于
var a
console.log(a) // undefined
a = 1
上面代码中,变量a
用var
命令声明时,会变量提示(Hoisting)了,那么使用let会怎样呢?
console.log(a) // ReferenceError: a is not defined
let a = 1
此时我们量a
用let
命令声明,被抛出错误ReferenceError: a is not defined
。此时从现象上来看好像是没有造成变量提升(Hoisting)。那么真实情况到底是怎样的呢?如果let
不会造成变量提升(Hoisting),我为什么会被大佬嘲笑呢?
从红宝书里寻找答案
下面我们试着从红宝书(JavaScript高级程序设计)里找找答案。
var 声明提升
在红宝书中的第3.3 变量中是这样介绍var
声明提升的
大致来看与我所说的一样,不过它这里介绍了用 var
声明同一个变量的时候,也和变量提升(Hoisting)有关。
var a = 0
var a = 1
var a = 2
console.log(a) // 2
在JavaScript运行时的会等价于以下代码
var a = 0
a = 1
a = 2
console.log(a) // 2
let 暂时性死区 (temporal dead zone)
在红宝书中的第3.3 变量中是这样介绍 let
中提到了一个新的概念暂时性死区(temporal dead zone)。
在let使用声明变量,JavaScript 运行时也是会注意到后面的 let 声明,但是不能被引用。这写就是暂时性死区 (temporal dead zone)了。此时我们使用未声明的变量会抛出 ReferenceError: xxx is not defined
。
但在红宝书第4 4.2.2 变量声明却说严格的说let 在 JavaScript 运行时中也会被提升。难道红宝书出现错误导致说法不一样?
通过仔细观察会发现俩个说法没有存在冲突:
- let 与 var 的另一个重要的区别,就是 let 声明的变量不会在
作用域
中被提升. - 严格来讲,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.
意译成中文:通过 let
与 const
声明定义了作用域的变量到正在运行的执行上下文(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
错误。
整体流程如下:
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
的创建阶段与初始化阶段是同时完成的直接进入赋值阶段
整体流程如下:
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.
意译成中文:如果一个常量在绑定阶段没有被赋值那么这就一个语法错误
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
但是换成 var
或者 const
也是会报错,但报错内容不一样
let var = 1 // Uncaught SyntaxError: Unexpected token 'var'
到此这个分析就结束了,看的头晕晕的,不说去睡觉了。睡前还要麻烦各位老哥动动小手点点三联。当然也欢迎各位老哥在下方留言说出自己的看法。
转载自:https://juejin.cn/post/7054205477571264549