Oneul Code - 10. Understanding const, Shallow Freeze, and Deep Freeze in JavaScript

2025-09-29

자바스크립트의 const와 객체 불변성: 얕은 동결 vs 깊은 동결

현재 저는 부트캠프 동료분들과 You Don’t Know JS 책 스터디를 진행중입니다. 제가 맡은 챕터는 자바스크립트의 기본 중의 기본인 변수와 함수 파트였습니다.

이 부분을 깊이 있게 학습하면서, 우리가 현재 배우고 있는 리액트(React)의 컴포넌트 생명주기나 렌더링 순서처럼, 자바스크립트 엔진 역시 변수와 함수를 실행하는 정교하고 일관된 실행 메커니즘과 스코프 규칙을 가지고 있음을 깨닫게 되었습니다

단순히 ‘변수가 선언되었다’, ‘함수가 호출되었다’를 넘어, 내부적으로 어떤 순서와 방식으로 처리되는지를 탐구하던 중, 익숙한 호이스팅(Hoisting) 개념의 이면에 숨겨진 조금은 생소한 개념인 TDZ(Temporal Dead Zone, 시간적 사각지대)를 마주하게 되었습니다.

이번 글에서는 변수와 함수가 실행 컨텍스트 내에서 어떻게 생성되고 실행되는지 그 메커니즘을 심층적으로 살펴보고, 특히 let, const와 함께 등장하며 우리에게 혼란을 주는 TDZ가 정확히 무엇이며, 이것이 우리의 코드에 어떤 영향을 미치는지 자세히 다뤄보고자 합니다.


1. 변수 실행 메커니즘: 선언 → 초기화 → 할당

변수가 만들어지는 과정은 크게 3단계로 나눌 수 있습니다.

  1. 선언(Declaration) – 메모리 공간을 확보

  2. 초기화(Initialization) – 변수가 사용할 수 있는 상태로 세팅

  3. 할당(Assignment) – 실제 값을 집어넣음

console.log(a); // undefined
var a = 10;

위 코드를 실행하면 undefined가 출력됩니다. 왜냐하면 자바스크립트 엔진이 코드를 실행하기 전에 var a;를 스코프의 최상단으로 끌어올려 선언 + 초기화(undefined) 까지 해두기 때문입니다. 이것이 바로 호이스팅(Hoisting)입니다.

반면 letconst는 조금 다르게 동작합니다.

console.log(b); // ReferenceError
let b = 20;

여기서 b는 선언은 호이스팅되지만, 초기화가 지연됩니다. 이 초기화되기 전까지의 구간을 TDZ(Temporal Dead Zone, 일시적 사각지대) 라고 부릅니다.

  • var → 선언 + 초기화(undefined)까지 먼저 진행됨 → 접근 가능

  • let / const → 선언만 끌어올려지고 초기화는 나중에 실행 → 초기화 전 접근 시 ReferenceError


2. TDZ(Temporal Dead Zone)란?

TDZ는 변수가 선언되었지만 초기화되기 전까지 접근할 수 없는 구간을 의미합니다. 쉽게 말해, “변수를 사용하려면 제대로 선언하고 초기화 한다음에 사용해라”라고 엔진이 강제하는 안전장치입니다.

function example() {
  console.log(x); // ReferenceError
  let x = 5;
}
example();

여기서 x는 선언은 되었지만 초기화 전이므로 TDZ 상태입니다.

TDZ는 왜 필요할까요?

과거 var만 쓰던 시절, 변수는 선언과 동시에 undefined로 초기화되어 조용한 실패를 유발하곤 했습니다.

function legacy() {
  if (false) {
    var temp = 123;
  }
  console.log(temp); // undefined
}
legacy();

개발자 입장에서는 temp가 블록 안에만 있어야 한다고 생각했을 텐데, 함수 전체 스코프에 퍼져 있어서 스코프 오염이 발생합니다. 이로 인해 예상치 못한 오류를 유발하는 조용한 실패의 원인이었습니다. 이러한 문제를 해결하기 위해 2015년 6월 TC39위원회의 공식적인 해결책인 let/const + TDZ가 도입된 것입니다.


3. 함수 실행 메커니즘: 선언식 vs 표현식

변수와 마찬가지로, 함수도 선언 방식에 따라 호이스팅과 TDZ 규칙이 달라집니다.

3-1. 함수 선언식 (Function Declaration)

sayHi();

function sayHi() {
  console.log("Hello!");
}
  • 동작 이유: 함수 전체가 호이스팅되어 실행 컨텍스트 생성 단계에서 메모리에 등록

  • 장점: 어디서든 호출 가능

  • 단점: 코드 흐름 추적이 어려움, 복잡한 코드에서는 디버깅 힘듦

3-2. 함수 표현식 (Function Expression)

sayHello(); // ReferenceError (TDZ 발생)

const sayHello = function () {
  console.log("Hi there!");
};
  • 동작 이유: 함수는 변수에 할당되므로 const/let 규칙을 따릅니다.

  • TDZ: 선언 전 접근 시 ReferenceError 발생

  • 장점: 실행 순서가 명확하여 코드 예측 가능, 유지보수 용이

결론

  • 함수 선언식 → 호이스팅됨 → 어디서든 호출 가능

  • 함수 표현식 → TDZ 영향 받음 → 선언된 이후에만 호출 가능

  • 현대 JS에서는 함수 표현식 + const 조합이 권장됩니다.


4. 실행 컨텍스트와 변수/함수 생성

4-1. 실행 컨텍스트(Execution Context)

자바스크립트 엔진은 코드 실행 시 실행 컨텍스트를 생성합니다.

  • 변수 환경(Variable Environment): 스코프 내 변수 저장

  • 렉시컬 환경(Lexical Environment): 참조 및 스코프 체인 관리

변수 생성 과정

  • Creation Phase: 메모리에 변수/함수 등록, 초기화 진행 (var는 undefined, let/const는 TDZ)

  • Execution Phase: 코드 한 줄씩 실행하며 값 할당

4-2. TDZ 시각화 예시

console.log(a); // ReferenceError
let a = 10; // TDZ 끝
console.log(a); // 10

타임라인:

|------ TDZ ------| (선언만 존재, 접근 불가)
let a = 10;        초기화 완료
------------------> (이제 접근 가능)

4-3. 블록 스코프 vs 함수 스코프

{
  var x = 1;
  let y = 2;
}
console.log(x); // 1
console.log(y); // ReferenceError
  • var → 함수 스코프

  • let/const → 블록 스코프

  • TDZ와 결합되면 코드 예측성과 안정성이 크게 향상


5. 실무적 관점: 왜 TDZ와 실행 메커니즘을 알아야 하는가?

TDZ, 호이스팅, 실행 컨텍스트는 단순히 이론적인 지식을 넘어 실제 프론트엔드 개발자의 생산성과 코드 안정성에 직접적인 영향을 미칩니다.

  • React와 클로저 이해: React 훅(Hook) 내부에서 상태(State)나 함수가 어떻게 실행 컨텍스트 내에 유지되고 클로저(Closure)를 형성하는지를 이해해야만, 예상치 못한 렌더링 순서 버그나 상태 업데이트 문제를 정확히 진단하고 해결할 수 있습니다.

  • 디버깅 시간 단축: let/const 변수를 초기화 전에 접근했을 때 발생하는 ReferenceError는 TDZ가 의도적으로 발생시키는 안전장치입니다. 이 원리를 명확히 알고 있다면, 에러 메시지를 보고 문제의 위치를 즉시 파악하여 디버깅 시간을 대폭 단축할 수 있습니다.

  • 견고하고 예측 가능한 코드: var 대신 let과 const를 사용하고 함수 표현식(const 사용)을 선호하는 현대 자바스크립트의 코딩 스타일은 궁극적으로 TDZ를 통해 스코프 오염을 방지하고 코드의 예측 가능성을 높이기 위함입니다. 이러한 원리를 이해하는 것이 ‘좋은 코드’를 작성하는 기본 토대입니다.


6. Oneul Code를 정리하며…

오늘 우리는 자바스크립트 코드를 단순히 사용하는 것을 넘어, 엔진이 코드를 내부적으로 어떻게 처리하는지 그 방식과 안전장치를 깊이 있게 이해하는 시간을 가졌습니다.

  1. 변수 실행 메커니즘: 변수는 선언 → 초기화 → 할당 단계를 거치며, var는 초기화까지 먼저 진행되지만, let/const는 선언만 호이스팅되고 TDZ가 발생합니다.

  2. TDZ (Temporal Dead Zone): let/const 변수가 선언은 되었으나 초기화 코드를 만나기 전까지 접근이 차단되는 영역입니다. 이는 과거 var로 인한 예기치 않은 스코프 오염과 조용한 실패를 방지하는 안전장치 역할을 합니다.

  3. 함수 실행 방식: 함수 선언식은 전체가 호이스팅되어 어디서든 호출 가능하지만, 함수 표현식은 변수의 const/let 규칙을 따라 TDZ의 영향을 받습니다.

  4. 스코프 차이: var는 함수 스코프를 따르지만, let/const는 블록 스코프를 따르므로, TDZ와 결합되어 코드의 예측성과 안정성이 크게 향상됩니다.

  5. 실무적 관점

  • React 훅, 렌더링 순서, 클로저 등 이해에 도움

  • TDZ 이해 시 초기화 전 접근 ReferenceError 예방 → 디버깅 시간 단축

  • 함수는 자바스크립트뿐 아니라 모든 프로그래밍 언어에서 핵심 개념이므로, 다양한 예제를 만들어 직접 호출하고 반환값을 확인해보는 것이 중요합니다.

Oneul Code는 오늘 배운 내용을 기록하며, 여러분도 직접 브라우저 콘솔에서 TDZ, 호이스팅, 함수 선언식과 표현식의 차이를 직접 사용해보시길 권장합니다. 자바스크립트의 내부 동작 원리를 이해하는 것은 복잡한 프론트엔드 환경에서 버그를 예방하고 효율적으로 디버깅하는 능력을 길러주는 가장 확실한 지름길이 될 것입니다.