Lighthouse of FE beginner

[JavaScript] var 그리고 let, const 본문

자바스크립트

[JavaScript] var 그리고 let, const

[FE] Lighthouse 2024. 11. 27. 18:31
모던 자바스크립트 Deep Dive 스터디 5회차

자바스크립트 스터디를 진행하며 해당 회차에 공부했던 내용을 직접 그림을 그리고 코드를 실행하며 저의 글로 작성합니다.

Overview

자바스크립트에서 변수를 선언하는데 사용하는 키워드는 var let const 가 있다.
var 키워드는 코드의 평가 단계에서 식별자의 선언과 초기화(undefined)가 동시에 발생하며 메모리에서 어떤 일들이 발생하는지 2회차 스터디에서 자세하게 살펴봤다. 이번 스터디에서는 let, const 키워드에 대해서 살펴본다.

[JavaScript] 변수와 데이터 타입

 

let 키워드는 재할당이 가능하고 선언과 초기화를 분리할 수 있다, const 키워드는 재할당이 불가능하며 선언과 초기화가 동시에 이뤄져야 한다, 이런 정보는 모두가 쉽게 알고 있는 정보이다. 이런 정보 외에 조금은 특별하다고 생각한 정보를 정리해보겠다.
 

let

let 키워드는 블록 레벨 스코프를 가진다. 블록 레벨 스코프란 코드의 블록을 모두 스코프로 평가한다는 것 이다.

let a = 1;

{
  console.log(a); //ReferenceError
  let a = 2;
}

 
위 코드는 책의 예제와 동일하다. let 키워드는 코드의 블록 {} 을 모두 스코프로 인정하기 때문에 4행에서 참조 에러가 발생한다. 즉 4행의 a는 스코프에 등록된 5행의 a를 참조 하려는 것 이기 때문이다.
 
여기서 한가지 더 살펴볼 수 있는 사실은 let 키워드 역시 호이스팅이 된다는 것이다. let 키워드는 코드의 평가 단계에서 선언과 초기화가 다른 시점에 진행이 된다.

var 키워드는 코드의 평가 단계에서 식별자의 선언과 암묵적인 초기화가 병행된다.

 
전역 스코프를 생성한 후 지역 스코프가 생성이 된다. 이때 5행의 a 변수는 선언이 되어 지역 스코프에 등록이 되고 (정확히는 실행 컨텍스트의 렉시컬 환경에 바인딩이 된다.) a 변수는 렉시컬 환경 레코드의 선언적 환경 레코드에 등록이 되기 때문에 코드를 만나서 초기화가 되기 전 까지 참조를 할 수 없게 된다. 이를 일시적 사각 지대(Temporary Dead Zone) 이라고 한다.
 

정리

  • 코드의 평가 단계에서 let 키워드의 변수는 선언이 된다.
  • 변수가 선언이 되면서 메모리에 공간을 할당 받지만 초기화가 발생하지 않아 실제로 값을 가리키진 않는다.
    • 코드의 평가 단계에서 식별자의 선언이 발생하기 때문에 식별자가 스코프에 관리가 된다. 하지만 TDZ에 의해 실제 선언문을 만나기 이전의 구역에서는 참조할 수 없다.
  • 식별자의 초기화 단계는 실제로 해당 선언문을 만난 시점에 발생한다. 초기화 단계에서 undefined로 초기화가 된다.

 

const

const 키워드 역시 let 키워드 처럼 TDZ를 가진다. 코드의 평가 단계에서 변수의 선언이 발생하기 때문이다.
 

Q. cosnt 키워드로 선언한 변수는 상수이다?

const 키워드로 선언한 변수는 상수일까? 결론부터 말하자면 원시 값이 할당된 const 키워드의 식별자는 상수처럼 동작하는 “값(리터럴)” 일 뿐, 상수는 아니다.


자바스크립트에서 원시 값은 불변성(Immutable)을 가진다고 했다.

[JavaScript] 원시 값과 객체 리터럴

 

즉 값을 변경한다는 것은 식별자가 참조하고 있는 메모리 주소가 가르키는 값을 변경하는 것이 아닌, 메모리에 새로운 공간을 할당하고 식별자가 해당 주소값을 바라본다는 것 이다.

 

불변한다는 것을 메모리의 관점에서 생각해야 한다. 그렇지 않으면 코드상에서는 값이 변하는데 왜 불변한다는 것일까 라는 의문점이 생기게 된다.

 

 

const 키워드는 선언과 할당을 동시에 진행해야하며 값을 재할당 할 수 없다.
 
위 말들을 종합해보면, const 키워드를 사용해 값을 선언하면 값을 재할당 할 수 없고, 변하지 않는 특성을 가진 자바스크립트의 원시 값을 리터럴로 사용한다면 값을 상수로 사용할 수 있다는 것이다.
 
자바스크립트에는 자바의 final과 같은 키워드가 존재하지 않는다. 하여 const 와 자바스크립트의 원시 값의 특성을 함께 이용해 상수처럼 사용할 수 있는 것이다.
 

변경 가능한 값, 객체

자바스크립트의 객체는 변경이 가능하다. 메모리에 선언된 객체 내부의 프로퍼티를 변경할 수 있다는 것이다.

 

const 키워드를 사용하면 값을 재할당 할 수 없다고 했다. 그럼 const 키워드로 변수를 선언하면 프로퍼티를 변경할 수 있을까? 객체의 프로퍼티를 변경하는 것은 가능하다.

const obj = {
  a: 1
};

obj.a = 2;

console.log(obj.a); // 2

 
원시 값은 변경이 불가능하고 const 키워드로 선언 시 재할당이 불가능 하기에 값을 변경할 수 없지만, 객체는 변경이 가능한 값이며, 프로퍼티를 변경하는 것은 변수에 값을 재할당을 하는 것이 아니기 때문이다.
 

객체를 활용한 네임스페이스

const 키워드를 사용하면 값을 상수처럼 활용할 수 있다고 했다. 그럼 같은 분류의 값들을 상수로 활용하고 싶다면 어떻게 해야할까?
 
TypeScript를 활용하고 있다면 as const 키워드를 활용해 객체를 변경 불가능 하게 만들 수 있다.

const OBJECT_MUTABLE = {
  A: "A",
  B: "B",
};
OBJECT_MUTABLE.A = "B";

const OBJECT_AS_CONST = {
  A: "A",
  B: "B",
} as const;
OBJECT_AS_CONST.A = "B"; // Compile Error

 
OBJECT_MUTABLE 객체는 일반 객체이며 OBJECT_AS_CONST 객체는 타입스크립트의 as const 을 활용해 const assertion(상수 단언)을 할 수 있다. as const를 통해 상수 단언된 객체는 readonly 객체로서 변경이 불가능하다.
 
실제로 위 코드의 11행 에서는 컴파일 에러가 발생하는데, 에러를 확인해보면 다음과 같다.

 
as const를 활용해 같은 분류의 값들을 객체화 시키고 코드의 흐름 상에서 변경 불가능하게 프로그래밍 할 수 있다. 다만 주의해야 할 점은 TypeScript는 JavaScript의 슈퍼셋 언어이고 자바스크립트로 컴파일이 되기 때문에 컴파일 된 언어는 결국 객체 프로퍼티의 변경이 가능하다는 것이다.
 
런타임 시점에 다이나믹하게 값이 변경이 될 수 있음으로 객체가 변하지 않도록 항상 사이드 이펙트에 주의하며 프로그래밍 하자.
 

var 키워드가 전역에 선언될 경우

해당 예제는 브라우저에서 동작합니다.

 
var 키워드가 전역에 선언될 경우 암묵적으로 전역 객체인 window 객체의 프로퍼티가 된다.

<script>
  var a = "global a";
  b = "global b";

  console.log(window.a, window.b);
</script>

 

 

 
let 키워드로 선언된 변수는 전역 객체의 프로퍼티가 아니다. let 키워드로 선언된 변수는 선언적 환경 레코드에 등록되기 때문이다. var 키워드를 사용해 전역 변수를 선언한다면 window 객체의 프로퍼티가 된다는 것에 주의하도록 하자.