>웹 프론트엔드 >JS 튜토리얼 >ECMAScript 표준의 프리즘을 통해 본 var, let 및 const의 차이점

ECMAScript 표준의 프리즘을 통해 본 var, let 및 const의 차이점

王林
王林원래의
2024-08-24 22:30:32547검색

The Differences Between var, let, and const Through the Prism of the ECMAScript Standard.

많은 기사에서 호이스팅, 시간적 데드존(TDZ), 기능적블록 범위 등은 표준을 참조하지 않는 경우가 많습니다. 이러한 용어 중 일부는 언어 표준에도 포함되지 않습니다. 언어 표준을 참조하지 않고 주제를 설명하는 것은 완벽합니다. 하지만 JavaScript를 포괄적으로 이해하려면 ECMAScript 표준을 이해하는 것이 중요하기 때문에 좀 더 깊이 알고 싶은 분들을 위해 참고하여 주제를 설명합니다.

ECMA스크립트

많은 조직에서 MDN Web Docs, javascript.info 등과 같은 JavaScript에 대한 참고 자료를 보유하고 있습니다. 그러나 컴퓨터 시스템을 표준화하고 문서화하는 것을 유일한 목적으로 하는 표준 조직이 하나 있습니다. 이 기관은 해당 분야의 믿을 수 있는 권위자 Ecma International입니다. 조직에서는 표준을 식별하기 위한 회사 내부 번호인 ECMA-262라는 표준을 유지 관리합니다. Ecma International은 이 표준을 우리가 일반적으로 JavaScript라고 부르는 ECMAScript 범용 프로그래밍 언어의 백본으로 정의합니다. 이 표준을 이해하는 것은 언어 자체를 이해하는 데 중요합니다. 2024년 8월 기준 최신 표준은

ECMAScript 2024라고도 알려진 ECMAScript 15판입니다.

실행 컨텍스트

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는 var, let, const 키워드의 동작을 이해하는 데 매우 중요합니다.


LexicalEnvironment와 VariableEnvironment는 모두 환경 레코드입니다. 환경 레코드는 ECMAScript 표준에 정의된 추상 데이터 구조이기도 합니다. 특정 변수 및 기능에 대한 식별자의 연관을 설정합니다. 식별자는 JavaScript의 값, 함수, 클래스 및 기타 데이터 구조를 참조하는 이름입니다. 다음 예에서 변수 = 42, 변수는 숫자 42의 값을 저장하는 변수의 이름(식별자)입니다.

코드가 실행될 때마다 실행 컨텍스트는 새로운 환경 레코드를 생성합니다. 식별자 저장 외에도 환경 레코드에는 null이거나 외부 환경 레코드에 대한 참조인 [[OuterEnv]] 필드가 있습니다.

그래픽적으로 이전 예의 실행 컨텍스트와 환경 레코드는 다음과 같이 표현될 수 있습니다.

// 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의 차이를 이해하는 데 매우 중요합니다.

Let 및 Const 선언

ECMAScript 표준의 14.3.1 Let 및 Const 선언 단락에는 다음이 명시되어 있습니다.

let 및 const 선언은 실행 중인 실행 컨텍스트의 LexicalEnvironment로 범위가 지정된 변수를 정의합니다. 변수는 포함된 환경 레코드가 인스턴스화될 때 생성되지만 변수의 LexicalBinding이 평가될 때까지는 어떤 방식으로든 액세스할 수 없습니다. 초기화 프로그램을 사용하여 LexicalBinding에 의해 정의된 변수에는 변수가 생성될 때가 아니라 LexicalBinding이 평가될 때 초기화 프로그램의 AssignmentExpression 값이 할당됩니다. 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.