通过 ECMAScript 标准的棱镜了解 var、let 和 const 之间的差异。
许多文章使用 提升、临时死区 (TDZ)、功能和块作用域等,通常不引用标准。其中一些术语甚至没有包含在语言标准中。在不参考语言标准的情况下解释该主题是完全可以的。不过,我通过引用来解释该主题,以便那些想要更深入地研究的人,因为理解 ECMAScript 标准对于全面掌握 JavaScript 至关重要。
许多组织都有 JavaScript 参考资料,例如 MDN Web Docs、javascript.info 等。然而,有一个标准组织的唯一目的是标准化和记录计算机系统。这个组织就是 Ecma International,该领域的可靠权威。该组织维护一个名为 ECMA-262 的标准,这是识别该标准的公司内部编号。 Ecma International 将此标准定义为 ECMAScript 通用编程语言(我们通常称为 JavaScript)的支柱。理解这个标准是理解语言本身的关键。截至 2024 年 8 月的最新标准是 ECMAScript 第 15 版,也称为
ECMAScript 2024。
要理解 var、let 和 const 之间的区别,有必要了解执行上下文的概念。
执行上下文是 ECMAScript 标准中定义的抽象结构。它是当前代码执行的环境。为了简化事情,我们可以假设存在全局执行上下文和功能执行上下文。
// Global Execution Context let globalVariable = 'This is a global variable' function outerFunction() { // Outer Function Execution Context let outerVariable = 'This is an outer variable' function innerFunction() { // Inner Function Execution Context let innerVariable = 'This is an inner variable' } innerFunction() } outerFunction()
LexicalEnvironment 和 VariableEnvironment 都是环境记录。环境记录也是 ECMAScript 标准中定义的抽象数据结构。它建立了标识符与特定变量和函数的关联。标识符是 JavaScript 中引用值、函数、类和其他数据结构的名称。在下面的例子中,让variable = 42,variable是存储数字42的值的变量名称(标识符)。
每次执行代码时,执行上下文都会创建一个新的环境记录。除了存储标识符之外,环境记录还有一个 [[OuterEnv]] 字段,可以为 null 或对外部环境记录的引用。
以图形方式,上一个示例中的执行上下文和环境记录可以表示如下:
// Global Execution Context { // Environment Record { identifier: 'globalVariable' value: 'This is a global variable' } { identifier: 'outerFunction' value: Function } [[OuterEnv]]: null } // Outer Function Execution Context { // Environment Record { identifier: 'outerVariable' value: 'This is an outer variable' } { identifier: 'innerFunction' value: Function } [[OuterEnv]]: Global Execution Context } // Inner Function Execution Context { // Environment Record { identifier: 'innerVariable' value: 'This is an inner variable' } [[OuterEnv]]: Outer Function Execution Context }
创建阶段和执行阶段。这两个阶段对于理解 var 和 let 或 const 之间的区别至关重要。
ECMAScript 标准第 14.3.1 Let 和 Const 声明中规定了以下内容:
let 和 const 声明定义了作用域为运行执行上下文的 LexicalEnvironment 的变量。这些变量是在实例化其包含的环境记录时创建的,但在评估变量的 LexicalBinding 之前不能以任何方式访问。由带有初始化程序的 LexicalBinding 定义的变量在计算 LexicalBinding 时(而不是在创建变量时)被分配其初始化程序的赋值表达式的值。如果 let 声明中的 LexicalBinding 没有初始值设定项,则在对 LexicalBinding 求值时,该变量将被赋予未定义的值。为了理解这句话,我会逐句解释。
let 和 const 声明定义作用域为正在运行的执行上下文的 LexicalEnvironment 的变量。这意味着使用 let 或 const 关键字创建的变量的作用域是定义它们的块。代码块是大括号内的任意 JavaScript 代码。
let condition = true if (condition) { let blockScopedVariable = 'This is a block-scoped variable' console.log(blockScopedVariable) // This is a block-scoped variable } console.log(blockScopedVariable) // ReferenceError: blockScopedVariable is not defined // Global Execution Context { // Environment Record { identifier: 'condition' value: true } [[OuterEnv]]: null // Block Environment Record { identifier: 'variable' value: 'This is a block-scoped variable' } [[OuterEnv]]: Global Execution Context }
变量在实例化其包含的环境记录时创建,但在评估变量的 LexicalBinding 之前不能以任何方式访问。
As previously mentioned, the Execution Context has two phases. This statement means that during the Creation Phase of the Execution Context, variables are stored in their corresponding Environment Record but have not yet been assigned any value. They are uninitialised.
console.log(varaible) // ReferenceError: Cannot access 'varaible' before initialization let varaible = 42 // Global Execution Context Creation Phase { // Environment Record { identifier: 'variable' value: uninitialised } [[OuterEnv]]: null }
Because the variable is already created (instantiated) in the Environment Record, the Execution Context knows about it but can't access it before evaluation(the Execution Phase of the Execution context). The state of the variable being uninitialised is also known as a Temporary Dead Zone(TDZ). We would have a different error if the variable hadn't been created in the Environment Record.
console.log(varaible) // ReferenceError: varaible is not defined // Global Execution Context Creation Phase { // Environment Record { } [[OuterEnv]]: null }
A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created.
LexicalBinding is a form of the Identifier, which represents the variable's name. The Initializer is the variable's value, and AssignmentExpression is the expression used to assign that value to the variable's name, such as the '=' sign in let variable = 42. Therefore, the statement above means that variables created with let or const keywords are assigned their value during the Execution Phase of the Execution Context.
let variable = 42 // Global Execution Context Creation Phase { // Environment Record { identifier: 'variable' value: uninitialised } [[OuterEnv]]: null } // Global Execution Context Execution Phase { // Environment Record { identifier: 'variable' value: 42 } [[OuterEnv]]: null }
If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.
This means that if a let variable is created without an initial value, undefined is assigned to it during the Execution Phase of the Execution Context. Variables declared with the const keyword behave differently. I will explain it in a few paragraphs later.
let variable // Global Execution Context Creation Phase { // Environment Record { identifier: 'variable' value: uninitialised } [[OuterEnv]]: null } // Global Execution Context Execution Phase { // Environment Record { identifier: 'variable' value: undefined } [[OuterEnv]]: null }
The standard also defines a subsection called 14.3.1.1 'Static Semantics: Early Errors,' which explains other essential aspects of variables defined with the let and const keywords.
LexicalDeclaration: LetOrConst BindingList;
- It is a Syntax Error if the BoundNames of BindingList contains "let".
- It is a Syntax Error if the BoundNames of BindingList contains any duplicate entries. LexicalBinding : BindingIdentifier Initializer
- It is a Syntax Error if Initializer is not present and IsConstantDeclaration of the LexicalDeclaration containing this LexicalBinding is true.
LetOrConst is a grammar rule which specifies that variable declarations can start with the let or const keywords.
BindingList is a list of variables declared with let or const keywords. We could imagine BindingList as a data structure like this:
let a = 1 let b = 2 let c = 3 const d = 4 const e = 5 BindingList: [ { identifier: 'a', value: 1 }, { identifier: 'b', value: 2 }, { identifier: 'c', value: 3 }, { identifier: 'd', value: 4 }, { identifier: 'e', value: 5 } ]
A Syntax Error is an error that breaks the language's grammatical rules. They occur before the code's execution. Let's analyse the first Syntax Error.
- It is a Syntax Error if the BoundNames of BindingList contains "let".
The BoundNames of BindingList are the names of variables declared with let or const keywords.
let a = 1 let b = 2 let c = 3 const d = 4 const e = 5 BoundNames: ['a', 'b', 'c', 'd', 'e']
A Syntax Error will occur when the BoundNames list contains “let”.
let let = 1 // SyntaxError: let is disallowed as a lexically bound name const let = 1 // SyntaxError: let is disallowed as a lexically bound name
- It is a Syntax Error if the BoundNames of BindingList contains any duplicate entries.
It means we can't use the same names for variables declared with the let or const keywords if they are already used in that scope.
let a = 1 let a = 2 // SyntaxError: Identifier 'a' has already been declared
- It is a Syntax Error if Initializer is not present and IsConstantDeclaration of the LexicalDeclaration containing this LexicalBinding is true.
IsConstantDeclaration is an abstract operation in the standard that checks if the variable is declared with the const keyword. This rule could be decrypted like that: if IsConstantDeclaration is true and the variable doesn't have an Initializer, a Syntax Error will be returned.
const x; // SyntaxError: Missing initializer in const declaration
Another vital thing only related to the const keyword: variables declared with the const keyword can't be reassigned. It is not stated explicitly in the standard, but we can get it from the IsConstantDeclaration operation and the syntax rule that variables declared with the const keyword should always be initialised with the Initializer
const variable = 42 variable = 46 // TypeError: Assignment to constant variable
Variable Statement
Before 2015, when the ECMAScript 2015 wasn't released yet, only the var keyword was available to create a variable in JavaScript.
In the paragraph 14.3.2 Variable Statement of ECMAScript standard the following is stated:
A var statement declares variables scoped to the running execution context's VariableEnvironment. Var variables are created when their containing Environment Record is instantiated and are initialized to undefined when created. Within the scope of any VariableEnvironment a common BindingIdentifier may appear in more than one VariableDeclaration but those declarations collectively define only one variable. 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.
I again explain it sentence by sentence.
A var statement declares variables scoped to the running execution context's VariableEnvironment.
This means that variables declared with the var keyword are either function-scoped if declared inside a function or global-scoped if declared outside any function.
let condition = true if (condition) { var globalVariable = 'This is a global variable' } console.log(globalVariable ) // This is a global variable function outerFunction() { // Outer Function Execution Context var outerVariable = 'This is an outer variable' } outerFunction() // Global Execution Context { // Environment Record { identifier: 'condition' value: true } { identifier: 'globalVariable' value: 'This is a global variable' } { identifier: 'outerFunction' value: Function } [[OuterEnv]]: null } // Outer Function Execution Context { // Environment Record { identifier: 'outerVariable' value: 'This is an outer variable' } [[OuterEnv]]: Global Execution Context }
Var variables are created when their containing Environment Record is instantiated and are initialized to undefined when created.
During the Creation Phase of the Execution Context variables are assigned the undefined value. The process of assigning the undefined to a variable during the Creation Phase is often referred to as "hoisting" or declaration hoisting. It is worth mentioning that the terms "hoisting" or declaration hoisting are not included in the standard. However, it is a convention used by many developers to explain the availability of variables "before" they were declared.
console.log(globalVariable) // undefined var globalVariable = 'This is a global variable' // Global Execution Context Creation Phase { // Environment Record { identifier: 'globalVariable' value: undefined } [[OuterEnv]]: null }
Sometimes, it is explained that the code example above is possible because variables declared with the var keyword are "moved" to the top of the scope. However, nothing is moved anywhere; it is only possible by assigning the undefined value to the variable during the Creation Phase of Execution Context.
Within the scope of any VariableEnvironment a common BindingIdentifier may appear in more than one VariableDeclaration but those declarations collectively define only one variable.
BindingIdentifier is a more specific type of the Identifier. We used the Identifier term before to explain the name of a variable. While Identifier also refers to the variable's name, BindingIdentifier is only used in the context of the declaration of variables (function or other data structure).
let variable = 42 // BindingIdentifier console.log(variable ) // Identifier
Now, let's go back to explaining the sentence's meaning.
BindingIdentifier may appear in more than one VariableDeclaration
In the same scope, we can create multiple variables with the same name using the var keyword, whilst all these "variables" reference only one variable.
var variable = 42 var variable = 66 var variable = 2015 // Execution context { // Environment Record { identifier: 'variable ' value: 2015 } [[OuterEnv]]: null }
It may appear we declared three variables with the BindingIdentifier variable, but we just reassigned the original variable variable twice. First, we reassigned it from 42 to 66, then from 66 to 2015
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.
The variable's value (Initializer) is assigned to it during the Execution Phase, not the Creation Phase of the Execution Context. Variables declared with the let and const keywords behave identically.
var variable = 42 // Global Execution Context Creation Phase { // Environment Record { identifier: variable value: undefined } [[OuterEnv]]: null } // Global Execution Context Execution Phase { // Environment Record { identifier: variable value: 42 } [[OuterEnv]]: null }
Diffrences
To sum up the article, I would like to highlight the following differences:
Scope
The first difference between variables created with var, let, and const keywords is how they are scoped. Variables created with let and const are scoped to the LexicalEnvironment, meaning they are available in the Environment Record of a block, function, or the Global Execution Context. In contrast, variables created with var are scoped to the VariableEnvironment, meaning they are only available in the Environment Record of a function or the Global Execution Context.
Creation Phase of the Execution Context
During the Execution Context's Creation Phase, variables created with let and const are uninitialised, whilst var variables are assigned the undefined value. The state of let and const being uninitialised is sometimes referenced as a Temporal Dead Zone or TDZ. Also, the behaviour of var being assigned the undefined value is usually known as “hoisting”.
Default Initializer value
Variables created with let and var keywords are assigned the undefined value if Initializer is not provided. Meanwhile, const variables must always have Initializer.
变量命名
使用 var 关键字创建的变量可以具有重复的名称,因为它们都引用相同的变量。但是,let 和 const 变量不能有重复的名称 - 这样做会导致语法错误。
变量初始化器重新分配
使用 let 和 var 关键字创建的变量可以将其初始初始化器(值)重新分配给不同的初始化器。但是,const 变量不能重新分配其初始化器。
以上是通过 ECMAScript 标准的棱镜了解 var、let 和 const 之间的差异。的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

实现视差滚动和元素动画效果的探讨本文将探讨如何实现类似资生堂官网(https://www.shiseido.co.jp/sb/wonderland/)中�...

学习JavaScript不难,但有挑战。1)理解基础概念如变量、数据类型、函数等。2)掌握异步编程,通过事件循环实现。3)使用DOM操作和Promise处理异步请求。4)避免常见错误,使用调试技巧。5)优化性能,遵循最佳实践。

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

探索前端中类似VSCode的面板拖拽调整功能的实现在前端开发中,如何实现类似于VSCode...
