ECMAScript6 (2) let, const

2023. 9. 2. 15:20자바스크립트 공부

728x90
반응형

※ 본 글은 EXMAScript6 두고두고 보는 자바스크립트 표준 레퍼런스 (김영보 지음)을 정리한 글임을 알립니다.

 

2.1 var키워드

 

var 키워드는 변수를 선언할 때 사용합니다. 크게 로컬 변수와 글로벌 변수로 구분하는데 그 이유는 스코프 때문입니다.

로컬 변수는 함수 또는 오브젝트를 스코프로 사용하려는 의도이며, 글로벌 변수는 프로그램 전체에서 공용으로 사용하려는 의도입니다. 글로벌 변수는 다른 프로그램에서 같은 이름의 변수를 사용할 수 있으므로 의도하지 않는 값이 설정될 수 있으니 사용에 유의해야 합니다. 

 

글로벌 변수 오해

 

글로벌 오브젝트에 작성한 변수는 글로벌 오브젝트가 스코프입니다. 글로벌 오브젝트에 작성했으므로 글로벌 변수라고 하는 것이지, 글로벌 오브젝트에서 보면 이는 로컬 변수입니다.

 

글로벌 변수가 편리해 보이지만, 객체지향 관점에서 보면 단점이라고 할 수 있습니다. 비유하자면 내가 사는 집(글로벌 오브젝트)의 물건(글로벌 변수)을 아무나 사용할 수 있는 모습입니다. 다른 사람이 내 허락도 없이 내 물건을 사용하는 것은 객체 지향 기본에서 어긋납니다.

 

글로벌 변수는 글로벌 오브젝트의 로컬 변수로 선언하고 사용해야 합니다. 함수 안에서 글로벌 오브젝트에 작성된 글로벌 변수를 사용할 수 는 있지만, 다른 프로그램에서 값을 변경할 수도 있다는 위험을 감수해야 합니다.

 

2.2 let 키워드

 

let 키워드는 var 키워드의 문제덤을 해결하기 위한 것으로 다음과 같은 특징이 있습니다.

 

1. 함수 안에 작성한 let 변수는 함수가 스코프입니다.

 

2. 함수 안에 if 블록의 형태로 코드를 작성했을 때, if 블록 내부의 let 변수의 스코프는 함수가 아닌 if 문의 블록 { } 이 스코프입니다.

 

3. 블록 { } 밖에 같은 이름의 변수가 있어도 스코프가 다르므로 변수 각각에 값을 설정할 수 있으며 변수 값이 유지됩니다.

 

4. 블록 안에 블록을 계층적으로 작성하면 각각의 블록이 스코프입니다.

 

5. 같은 스코프 안에서 같은 이름의 let 변수를 선언할 수 없습니다.

 

 

※ 원래 6번의 경우  "let 변수는 호이스팅되지 않습니다."이지만,  호이스팅이 된다는 것과 Temporal Dead Zone이라는 개념이 있는 것으로 압니다. 이 내용은 아래의 호이스팅과 Temporal dead zone에 관한 정리를 참고해주세요!

 

2.3 let 변수 작성 방법

 

let name1 [= value1] [, name2 [= value2]] [, nameN [= valueN]];

 

name1, name2, nameN에 변수 이름을 작성합니다. value에는 변수에 할당할 값을 작성합니다. 변수에 초깃값을 할당하지 않고 선언만 할 수 있습니다.

 

변수에 값을 할당하지 않고 선언만 하면 디폴트 값으로 undefind가 할당되며, 자바스크립트에서 undefind는 값입니다.

 

만약 같은 스코프에서 같은 이름의 변수를 let으로 선언한다면 Syntax Error(구문 에러)가 발생합니다. 컴파일 단계에서 발생하며 소스 코드에 하나라도 구문 에러가 발생한다면 전체 코드가 실행되지 않습니다.

 

2.4 블록 스코프

 

let 변수 선언의 가장 큰 목적은 스코프이며 그 중에서도 블록 스코프가 돋보입니다. 블록 스코프의 기준은 블록을 나타내는 중괄호 { }입니다. 블록 안과 밖의 변수 이름이 같더라도, 스코프가 다르기 때문에 각 변수에 할당된 값이 대체되지 않고 유지됩니다.

 

2.5 let과 this 키워드

 

블록 스코프에서 this 키워드가 참조하는 오브젝트를 살펴봅시다.

 

var music = "음악";
console.log(this.music);

var sports = "축구";
console.log(this.sports);

// 실행 결과

음악
undefind

var 키워드로 선언한 music 변수의 스코프는 글로벌 오브젝트이고 this가 글로벌 오브젝트를 참조합니다. music을 콘솔로 확인하면 변수의 값인 음악이 출력됩니다.

 

글로벌 오브젝트는 window 오브젝트가 아니지만, window 오브젝트로 글로벌 변수에 접근할 수 있습니다. 크롬의 개발자 도구에서 this가 window 오브젝트를 참조하는 것을 볼 수 있습니다. 이때, window 오브젝트는 글로벌 오브젝트를 의미합니다.

 

논리적으로는 자바스크립트에 글로벌 오브젝트는 있지만 window 오브젝트는 없습니다. window 오브젝트는 DOM 메서드도 있고 window 오브젝트 자체의 프로퍼티도 존재합니다. 이처럼 window 오브젝트에 글로벌 오브젝트가 존재하는 개념이라고 이해할 수 있습니다.

 

let 키워드로 선언한 sports를 콘솔로 출력하면 undefind가 나옵니다. 이는 this가 window 오브젝트를 참조하는데 내부에 let 변수가 없다는 것으로, window 오브젝트에 let 변수가 설정되지 않는다는 의미입니다. 이것이 var 변수와 let 변수의 차이입니다. (const도 마찬가지입니다.)

 

2.6 function

 

함수도 스코프를 가지므로 하나의 블록 스코프입니다. ES5에서는 함수에 선언된 모든 변수가 함수 스코프에 속하지만, ES6에서는 함수 안의 if 블록은 다른 스코프이므로 계층 구조의 스코프가 형성됩니다.

 

이 말은 let을 이용하여 전역 변수로 sports에 축구를 할당하고, 함수 내부의 sports에 농구를 할당한 후 함수 내부에서 콘솔로 sports를 출력하면 농구가 출력됩니다.

 

즉, let 변수도 가장 가까운 스코프에 있는 변수를 먼저 사용한다의 것의 의미합니다.

 

2.7 try-catch

 

try-catch문에서 try 블록 { } 기준으로 블록 스코프를 가지며, catch 블록은 스코프를 갖지 않으며 try 블록 스코프에 속합니다.

 

2.8 switch-case

 

switch-case 문에서 switch 블록이 스코프 블록입니다. switch 안의 case는 별도의 스코프를 갖지 않으며 switch의 스코프에 속합니다.

 

2.9 호이스팅

 

자바스크립트는 소스 코드를 위에서 아래로 순차적으로 실행합니다. 따라서 호출될 함수를 작성한 후, 아래에서 함수를 호출해야 함수가 호출됩니다. 하지만 함수 선언문은 함수를 호출하는 코드를 위에 작성하고 호출된 함수를 아래에 작성해도 함수가 호출됩니다. 이를 호이스팅이라고 하며, 스코프 안에 존재하는 모든 선언들을 해당 스코프의 최상단으로 끌어올리는 것을 말합니다.

 

 

※ 이 내용은 아래의 호이스팅과 Temporal dead zone에 관한 정리를 참고해주세요!

 

2.10 for()

 

반복문인 for (var k = 0; k < 5; k++) { } 형태인 경우 k를 var 변수로 작성한 것과 let 변수로 작성하는 것에는 차이가 있습니다. for( ) 문에서 let 변수는 반복할 때마다 스코프를 갖는 반면, var 변수는 스코프를 갖지 않습니다.

 

그렇기에 만약 html에서 li에 index를 부여하는 경우 var는 모두 5가 나오겠지만, let은 각각의 값을 갖게 됩니다.

 

2.11 const

 

const 키워드는 변수에 할당된 값을 변경할 수 없는 상수를 선언할 때 사용합니다. 다만 객체의 프로퍼티 값은 변경할 수 있습니다.

 

https://product.kyobobook.co.kr/detail/S000001891056

 

ECMAScript 6 | 김영보 - 교보문고

ECMAScript 6 | 355개의 풍부한 예제와 간략하면서 핵심을 파고 든 해석!웹 개발자라면 언젠가 한 번은 꼭 넘어야 할 산이 있다. 바로 ECMAScript(ES)이다. 저자는 이 책의 서두에 'ES6를 이해하지 못하고

product.kyobobook.co.kr

 


 

호이스팅과 Temporal dead zone에 관한 정리

호이스팅(hoisting)이란?

우선 hoist라는 단어의 뜻부터 살펴봅시다.

 

hoist : (흔히 밧줄이나 장비를 이용하여) 들어[끌어]올리다

 

다음과 같이, 끌어올린다는 뜻을 가지고 있습니다.


그렇다면 자바스크립트에서 hoisting은 어떤 의미를 가지는걸까요?

 

 

대부분 변수와 함수의 선언부가 각자의 현재 스코프 영역 최상단으로 옮겨지는 것이라고 얘기하며

 

 

코드가 시작되기 전에 변수, 함수 선언이 해당 스코프의 최상단으로 끌어 올려지는 것을 말합니다.


그러나! 정확히 말하면 끌어 올려지는 것처럼 보이는 현상을 말합니다. 즉, 물리적으로 코드가 위로 옮겨진다는 것이 아니고,  코드는 그 자리에 그대로 있습니다.

 

이를 이해하기 위해서는 자바스크립트 엔진이 어떻게 코드를 실행하는지 알아봐야합니다.

 

Lexical Environment

During compile phase, just microseconds before your code is executed, it is scanned for function and variable declarations. All these functions and variable declarations are added to the memory inside a JavaScript data structure called Lexical Environment. So that they can be used even before they are actually declared in the source code.- Sukhjinder Arora 's Medium -

 

 

 

 

컴파일 단계에서, 당신의 코드가 실행되기 몇 마이크로 초 전에, 함수와 변수 선언이 스캔됩니다.
스캔된 모든 함수, 변수 선언은 Lexical Environment라고 불리는 자바스크립트 데이터 구조 내 메모리에 추가됩니다.
따라서, 함수, 변수가 소스 코드 내에서 실제로 선언되기 이전부터 사용 할 수 있습니다.

 

 

자바스크립트 엔진은 코드를 실행하기 전에 Lexical Environment 내에 있는 메모리에 함수, 변수 선언을 추가한다고합니다.

 

이 덕분에, hoisting이라는 개념을 우리가 접할 수 있고, 눈으로 볼 수 있는 것입니다!

 

그러면 Lexical Environment라는 것은 어떻게 생겼을까요?

 

 

A lexical environment is a data structure that holds identifier-variable mapping. (here identifier refers to the name of variables/functions, and the variable is the reference to actual object [including function object] or primitive value).
- Sukhjinder Arora 's Medium -

 

 

 

Lexical Environment는 "identifier-variable" 매핑 정보를 가지고 있는 데이터 구조입니다.


"identifier"는 변수, 함수의 이름을 가리키며, "variable"은 실제 객체 혹은 원시값을 가리킵니다

 

 

즉, 아래와 같은 구조라는 것입니다.

LexicalEnvironment = {
  Identifier:  <value>,
  Identifier:  <function object>
}

즉 코드가 실행되는 동안 변수와 함수가 살고있는 공간이라고 보면 되겠습니다.

 

호이스팅 예시

함수 선언식의 호이스팅

helloWorld(); // Hello World!

function helloWorld(){
  console.log('Hello World!');
}

앞서 말했듯이, 함수 선언부는 컴파일 단계에서 메모리에 추가됩니다.
덕분에, 실제 함수 선언을 만나기 전에 함수에 접근할 수 있습니다.

 

Lexical Environment에서의 상황을 보면 아래와 같습니다.(컴파일 직전 몇 마이크로 초 전의 상황)

lexicalEnvironment = {
  helloWorld: < func >
}

따라서, 자바스크립트 엔진이 helloWorld()를 마주치면, 엔진은 Lexical Environment를 살펴보고, 함수를 찾아서 그 것을 실행시킬 수 있게 됩니다.

 

함수 표현식의 호이스팅

함수 선언식이 hoisting이 되는 것에 반해, 함수 표현식은 hoisting이 되지않습니다.

helloWorld(); // TypeError: helloWorld is not a function

var helloWorld = function () {
  console.log("Hello World!");
};
helloWorld(); // ReferenceError: Cannot access 'helloWorld' before initialization

const helloWorld = function () {
  console.log("Hello World!");
};

var, let, const 모두 에러가 발생하지만 에러 내용이 다릅니다!


이 부분은 뒤에서 다루겠습니다. 일단, 함수 표현식은 호이스팅이 되지않는 것을 알 수 있습니다.

 

왜 그럴까요?

 

 

As JavaScript only hoist declarations, not initializations (assignments), ....
- Sukhjinder Arora 's Medium -

 

 

 

자바스크립트는 오직 선언만을 hoist한다. 초기화(할당)를 hoist하는 것이 아니다.

 

 

위와 같은 이유로 인해서, helloWorld는 함수가 아니라 변수로 취급이 됩니다.


helloWorld가 var 변수이기 때문에, hoisting 중에는 엔진이 undefined를 할당 할 것입니다.

따라서, 함수 표현식을 실행하기 위해서는 아래와 같이 사용해야합니다.

 

var helloWorld = function(){
  console.log('Hello World!');
}

helloWorld(); // Hello World!

함수를 먼저 만들고, 사용은 그 아래에서 해야한다는 것입니다.

 

var의 호이스팅

console.log(a); // undefined

var a = 3;

undefined가 출력되었습니다. 그 이유는 위에서 얘기한

 

자바스크립트는 선언을 hoist하는 것이지, 초기화(할당)을 hoist하지는 않는다.

 

반복해서 말하지만 컴파일 단계에서 오직 변수, 함수 선언을 저장하는 것이지 초기화(할당)을 저장하는 것이 아닙니다.

 

왜 undefined인가요?

 

 

When JavaScript engine finds a var variable declaration during the compile phase, it will add that variable to the lexical environment and initialize it with undefined and later during the execution when it reaches the line where the actual assignment is done in the code, it will assign that value to the variable.
- Sukhjinder Arora 's Medium -

 

 

 

자바스크립트 엔진이 컴파일 단계에서 var 변수 선언을 찾았을 때, 엔진은 변수를 Lexical Environment에 저장하고 undefined로 초기화합니다.

 

나중에 코드를 실행하며 실제로 할당이 이뤄지는 코드 라인에 도달했을 때, 엔진은 변수에 값을 할당합니다.

 

 

따라서, 초기 Lexical Environment는 아래와 같습니다.

lexicalEnvironment = {
  a: undefined
}

 

이로 인해서, 위 예시에서 undefined가 출력된 것입니다.

 

엔진이 코드를 실행하며 할당이 이뤄지는 코드 라인을 만나면, Lexical Environment 내에서 변수의 값을 업데이트하는데, 그 때의 Lexical Environment는 아래와 같습니다.

 

lexicalEnvironment = {
  a: 3
}

즉, 아래와 같은 형태로 코드가 작동하고 있는 것이라고 볼 수 있겠습니다.

var a;

console.log(a); // undefined

a = 3;

다만, 주의할 것은 이 내용은 var의 hoisting에 대한 것이지, 모든 변수의 hoisting에 대한 것이 아니라는 점입니다.

 

let, const의 호이스팅

 

console.log(a); // ReferenceError: Cannot access 'greeting' before initialization

let a = 3;
console.log(a); // ReferenceError: Cannot access 'greeting' before initialization

const a = 3;

이번에는 var에서와는 다르게 에러가 발생했습니다.


let, const는 hoisting이 안되는걸까요??

 

 

All declarations (function, var, let, const and class) are hoisted in JavaScript, while the var declarations are initialized with undefined, but let and const declarations remain uninitialized.
- Sukhjinder Arora 's Medium -

 

 

자바스크립트에서 모든 선언(함수, var, let, const, class ..)은 hoist된다.
var 선언이 undefined로 초기화되는 반면, let과 const 선언은 uninitialized로 남아있다.

 

hoisting이 안되는 것이 아니다. 모두 hoisting이 됩니다!


그러나, var는 undefined로 초기화까지 되는 반면 let, const는 uninitialized로 남아있다는 것입니다.

 

 

They will only get initialized when their lexical binding (assignment) is evaluated during runtime by the JavaScript engine. This means you can’t access the variable before the engine evaluates its value at the place it was declared in the source code. This is what we call “Temporal Dead Zone”, A time span between variable creation and its initialization where they can’t be accessed.
- Sukhjinder Arora 's Medium -

 

 

 

let, const의 초기화는 오직 자바스크립트 엔진 작동 중 lexical binding(할당)이 발생했을 때에만 이루어진다.
이는 엔진이 소스 코드 내에서 let, const가 선언된 장소에서 그 값을 평가하기 전까지는
당신이 변수에 접근할 수 없다는 것을 의미한다.
변수의 생성과 초기화 사이의 기간에 접근 할 수 없는 것을 "Temporal Dead Zone"이라고 한다.

 

let, const의 초기화는 자바스크립트 엔진이 해당 코드 라인에 도달했을 때에만 발생한다는 것입니다.


그 전에는 우리가 변수에 접근할 수 없습니다.

 

만약, let과 const가 선언된 부분에서 여전히 엔진이 값을 찾지 못한다면, undefined가 할당되거나, 에러를 발생시킵니다(const의 경우).

let a;

console.log(a); // undefined

a = 3;
const a;

console.log(a); // SyntaxError: Missing initializer in const declaration

a = 3;

let에 대한 예시를 단계별로 살펴봅시다.

 

컴파일 단계에서 자바스크립트 엔진이 변수 a를 만나면, Lexical Environment에 저장합니다.


그러나, let으로 선언된 변수이므로 엔진은 이를 어떠한 값으로도 초기화하지 않습니다.
따라서 Lexical Environment는 아래와 같습니다.

 

lexicalEnvironment = {
  a: <uninitialized>
}

만약, 변수가 선언되기 전에 접근하려고 하면,
자바스크립트 엔진은 Lexical Environment에서 변수의 값을 찾으려고합니다.


그러나 변수는 uninitialized 상태이므로 ReferenceError를 던집니다.

 

코드가 실행되는 중에, 자바스크립트 엔진이 변수가 선언된 코드 라인에 도달하게 되면 엔진은 변수의 값을 찾는데, 변수는 현재 연결되어 있는 값이 없으므로 undefined를 할당합니다.


그 때의 Lexical Environment는 다음과 같습니다.

lexicalEnvironment = {
  a: undefined
}

따라서, undefined가 콘솔에 찍힙니다.


그 후, 3이라는 값이 변수 a에 할당되며, Lexical Environment는 변수의 값을 업데이트합니다.


그 때의 Lexical Environment는 다음과 같습니다.

lexicalEnvironment = {
  a: 3
}

참고로, let, const가 선언되기 전에 접근할 수 있는 경우도 있습니다.

function foo() {
  console.log(a);
}

let a = 20;

foo(); // 20

물론 이 코드는 아래와 같이 사용하면 에러가 발생합니다.

function foo() {
  console.log(a); // ReferenceError: Cannot access 'a' before initialization
}

foo();

let a = 20;

TDZ(Temporal Dead Zone) 란?

 

TDZ(Temporal Dead Zone) 란, 한글로 직역하자면 일시적인 사각지대란 뜻입니다.

이 일시적인 사각지대는 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 TDZ(Temporal Dead Zone) 라고합니다.

 

변수 선언의 3단계

javascript에서의 변수는 위의 사진처럼 선언, 초기화, 할당이라는 3가지 단계의 걸쳐서 생성됩니다.


선언 단계(Declaration phase) : 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계를 의미합니다. 이 변수 객체는 스코프가 참조하는 대상이 됩니다.

초기화 단계(Initialization phase) : 실행 컨텍스트에 존재 하는 변수 객체에 선언 단계의 변수를 위한 메모리를 만드는 단계 입니다. 이 단계에서 할당된 메모리에는 undefined로 초기화 됩니다.

할당 단계(Assignment phase) : 사용자가 undefined로 초기화된 메모리의 다른 값을 할당하는 단계 입니다. 


여태까지 저희는 var혹은 let/const를 그냥 사용하였지만 사실은 위 3가지 단계를 거쳐서 생성되는 것 이었습니다.

그리고 var와 let/const의 차이는 이 3가지 단계의 순서에 차이가 존재합니다.

 

먼저 var 변수의 라이프사이클을 알아보도록하겠습니다.

 

위 사진은 var 키워드 변수의 라이프 사이클 입니다.

 

var 키워드 변수는 변수 선언전에 선언 단계와 초기화 단계를 동시에 진행합니다.

 

그래서 javascript는 실행 컨텍스트 변수 객체의 변수를 등록하고 메모리를 undefined로 만들어 버립니다.

그렇기 때문에 변수를 선언하기 전에 호출을 해도 undefined로 호출이 되는 호이스팅이 발생하는 것 입니다.

 

그렇다면 let의 라이프 사이클을 한번 봐보도록 하겠습니다.

let으로 선언된 변수는 var 키워드와는 다르게 선언단계와 초기화 단계가 분리되어서 진행이 됩니다.

 

그렇기 때문에 실행 컨텍스트에 변수를 등록했지만,

메모리가 할당이 되질 않아 접근할 수 없어 참조 에러(ReferenceError)가 발생하는 것이고,

이것을 보고 우리가 호이스팅이 되질 않는다!! 라고 오해할 수 밖에 없었던 것입니다.

 

그럼 우리가 처음 말했던 TDZ에 대해서 다시한번 알아보도록 하겠습니다.

 

TDZ는 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 말합니다.

 

즉, let 또한 선언전, 실행 컨텍스트 변수 객체에 등록이 되어 호이스팅이 되지만,

TDZ 구간에 의해 메모리가 할당이 되질 않아 참조 에러(ReferenceError) 발생하는 것입니다.


참고로 function 키워드 함수는 아래 사진과 같이 변수선언 3단계를 동시에 진행해 버립니다.

일반적으로 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 TDZ(Temporal Dead Zone)이라고 얘기하지만, 의미론적으로 변수의 생성과 초기화 사이의 기간에 접근 할 수 없는 것을 "Temporal Dead Zone"이라고 합니다.

 

즉, let과 const가 생성되어 렉시컬 환경 초기에 호이스팅되어 초기화되지 않은 형태로 저장된 시점에 접근할 수 없는 것을 이야기합니다.

 

위의 변수 선언 단계의 선언, 초기화, 할당은 위에서 설명한 렉시컬 환경에 저장되며 진행됩니다.

 

아래의 참고 블로그에 정말 잘 정리되어 있습니다! 꼭 참고해보세요.

참고 블로그

https://velog.io/@rkio/Javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85hoisting%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC

 

[Javascript] 호이스팅(hoisting)에 대하여

자바스크립트를 깊게 공부하려고 할 때, 자주 등장하는 단어 중 하나가 호이스팅(hoisting)이다. 많은 게시글들에서 함수 혹은 변수를 가장 상단으로 올려주는 효과를 내준다는 말을 한다. 오늘은

velog.io

 

https://noogoonaa.tistory.com/78

 

TDZ(Temporal Dead Zone)이란?

함께보면 좋은 글 2020/07/05 - [프로그래밍 언어/Javascript] - 자바스크립트 호이스팅(Hoisting)이란? 오늘은 TDZ(Temporal Dead Zone)에 대해서 알아보도록 하겠습니다. 이번 포스팅은 자바스크립트의 호이스

noogoonaa.tistory.com

 

 

 

728x90
반응형

'자바스크립트 공부' 카테고리의 다른 글

Scope란?  (0) 2023.09.04
프론트엔드 캐시란? + 보안 위험  (3) 2023.09.04
레이지로딩 (lazy loading)이란? + vue 적용 예시  (0) 2023.09.04
this 그리고 window 객체란?  (0) 2023.09.04
ECMAScript6 (1) 개요  (0) 2023.09.01