Cllaude99Cllaude99

var, let, const와 호이스팅의 동작 원리

·12 min read·Cllaude99
JavaScriptHoistingTDZ

JavaScript에서 변수를 선언할 때 사용하는 키워드에는 var, let, const가 있습니다. 이 세 가지 키워드는 각각 다른 특성을 가지고 있는데요, 이번글에서는 var, let, const에 대해 알아보겠습니다.

호이스팅이란?

먼저 호이스팅에 대해 알아보겠습니다.

MDN 문서에 따르면 JavaScript 호이스팅은 인터프리터가 코드를 실행하기 전에 함수, 변수, 클래스 또는 임포트(import)의 선언문을 해당 범위의 맨 위로 끌어올리는 것처럼 보이는 현상을 말합니다.

이는 변수나 함수가 실제 코드에서 작성된 위치와 관계없이 선언 단계에서 메모리에 저장되기 때문에 발생합니다.

이러한 호이스팅은 var, let, const에서 모두 이루어지지만, 각각 다르게 동작합니다.

var 키워드의 특징

var은 다음과 같은 특징을 가집니다.

  • 함수 스코프(Function Scope)
  • 재선언 및 재할당 가능
  • 호이스팅 시 undefined로 초기화

함수 스코프

아래는 함수 스코프를 가지는 var의 예시입니다.

function main() {
  if (true) {
    var a = 1;
  }
  console.log(a);
}

main(); // 1

var 키워드는 함수 스코프를 가지기 때문에 main 함수 내에서 선언된 변수 a는 함수 스코프 내에서 어디서든 접근할 수 있습니다. 블록({}) 안에서 선언되었지만, 블록을 벗어나도 함수 내에서는 접근 가능합니다.

재선언 가능

var a = 1;
var a = 2;
console.log(a); // 2

위와 같이 변수 a를 두 번 선언해도 에러가 발생하지 않습니다. 이는 의도하지 않은 변수 재선언으로 인한 버그를 발생시킬 수 있어 주의가 필요합니다.

var의 호이스팅

console.log(num); // undefined
var num = 20;

위 코드의 결과는 undefined입니다. num의 선언 단계에서 메모리에 저장되어 호이스팅이 발생하기 때문에 undefined가 출력됩니다.

실제로 JavaScript 엔진은 위 코드를 아래와 같이 해석합니다.

var num; // 호이스팅으로 선언이 맨 위로 끌어올려짐
console.log(num); // undefined
num = 20; // 할당은 원래 위치에서 실행

let과 const 키워드의 특징

ES6에서 도입된 let과 const는 var의 문제점을 보완하기 위해 등장했습니다.

let과 const는 다음과 같은 특징을 가집니다.

  • 블록 스코프(Block Scope)
  • 재선언 불가능
  • 호이스팅 시 TDZ(Temporal Dead Zone)에 위치

블록 스코프

function main() {
  if (true) {
    let a = 1;
  }
  console.log(a); // 참조 에러: a가 정의되지 않았습니다
}

main();

위 코드에서 let은 블록 스코프를 가지기 때문에 블록 스코프 내에서 선언된 변수 a는 블록 스코프 내에서만 접근할 수 있습니다.

function main() {
  let x = 'hello';
  if (true) {
    let x = 'world';
    console.log(x); // world
  }
  console.log(x); // hello
}

main();

마찬가지로 let은 블록 스코프를 가지기 때문에 블록 내부와 외부의 x는 서로 다른 변수입니다.

재선언 불가능

let a = 1;
let a = 2; // 문법 에러: 'a'가 이미 선언되었습니다

const b = 1;
const b = 2; // 문법 에러: 'b'가 이미 선언되었습니다

let과 const는 재선언이 불가능합니다. 이는 var의 재선언 문제를 해결하여 더 안전한 코드를 작성할 수 있게 해줍니다.

let과 const의 차이점

차이점이 있다면 let은 재할당이 가능하지만 const는 재할당이 불가능하다는 것입니다.

let a = 1;
a = 2; // 정상 동작
console.log(a); // 2

const b = 1;
b = 2; // 타입 에러: 상수 변수에 할당할 수 없습니다

위의 경우, let은 재할당이 가능하여 에러가 발생하지 않지만, const는 재할당이 불가능하기 때문에 에러가 발생합니다.

const의 특별한 규칙

const의 경우 선언을 함과 동시에 값을 할당해주어야 합니다. 즉 선언만 하고 값을 비울 수 없습니다.

const a; // 문법 에러: const 선언에 초기화가 누락되었습니다
const b = 1; // 정상 동작

하지만 const의 경우 일반적으로 값을 변경할 수 없지만 객체의 경우 속성값을 변경할 수 있습니다.

const obj = { a: 1 };
obj.a = 2;
console.log(obj); // { a: 2 }

위의 경우, obj의 속성값을 변경할 수 있기 때문에 2가 출력됩니다. const는 재할당을 막는 것이지, 객체 내부의 속성 변경까지 막는 것은 아닙니다.

만약 객체의 값을 변경하지 않기를 원한다면 Object.freeze를 사용하여 객체를 동결할 수 있습니다.

const obj = { a: 1 };
Object.freeze(obj);
obj.a = 2;
console.log(obj); // { a: 1 }

위의 경우, obj의 속성값을 변경할 수 없기 때문에 1이 출력됩니다.

let과 const의 호이스팅과 TDZ

let과 const 또한 호이스팅이 발생합니다.

console.log(num); // 참조 에러: 초기화 전에 'num'에 접근할 수 없습니다
let num = 20;

위의 경우 참조 에러가 발생해서 호이스팅이 발생하지 않는다고 생각할 수 있지만 그렇지 않습니다. TDZ(Temporal Dead Zone)에 의해 접근이 불가능한 것입니다.

TDZ란?

TDZ란 변수가 선언되었지만 아직 초기화되지 않은 상태를 말합니다. 즉, '선언만 되고 아직 초기화되지 않은 변수가 머무는 공간'이라고 생각하면 됩니다.

JavaScript에서는 let이나 const로 선언한 변수들이 TDZ를 거칩니다. 이때 이 공간에 있는 변수를 참조하려고 하면 ReferenceError가 발생합니다.

TDZ가 필요한 이유

TDZ는 초기화되지 않은 변수를 사용하는 것을 방지하여 프로그래밍 오류를 줄이는 데 도움을 줍니다.

결과적으로 let과 const는 호이스팅되어 변수를 메모리에 올려놨지만 TDZ라는 영역에 있어 해당 변수를 초기화하는 코드 줄을 지나야 접근이 가능합니다.

// TDZ 시작
console.log(letVariable); // 참조 에러 (TDZ 내부)
// TDZ 끝
let letVariable; // 초기화 지점
console.log(letVariable); // undefined (TDZ를 벗어남, 하지만 아직 값이 할당되지 않음)
letVariable = '초기화 완료';
console.log(letVariable); // '초기화 완료'

위와 같이 let으로 선언한 letVariable은 별도의 값을 할당하지 않을 경우 undefined로 초기화됩니다. 이후 letVariable에 값을 할당하면 TDZ를 벗어나 참조 에러가 뜨지 않고 초기화된 값이 출력됩니다.

함수 호이스팅

변수에 대해 알아보았으니 함수에 대해서도 알아보겠습니다.

JavaScript에서는 함수를 정의하는 방식에 따라 함수 선언식함수 표현식으로 나눌 수 있습니다.

함수 선언식의 호이스팅

함수 선언식은 이름이 있는 함수로 JavaScript 엔진이 코드를 실행하기 전에 메모리에 로드하기 때문에 호이스팅이 발생합니다.

console.log(add(2, 3)); // 5

function add(a, b) {
  return a + b;
}

여기서 add 함수는 선언된 위치보다 앞서 호출되어도 정상적으로 실행됩니다. 이는 JavaScript 엔진이 함수 선언을 미리 메모리에 로드했기 때문입니다.

함수 표현식의 호이스팅

반면, 함수 표현식은 변수에 익명 함수를 할당하는 방식으로 할당된 변수명으로 호출할 수 있습니다. 함수 표현식은 변수 호이스팅 규칙을 따르며, 변수에 할당된 이후에만 호출할 수 있습니다.

console.log(add(2, 3)); // 참조 에러: 초기화 전에 'add'에 접근할 수 없습니다

const add = function (a, b) {
  return a + b;
};

위의 경우 const로 선언된 변수 add는 TDZ에 있기 때문에 참조 에러가 발생합니다.

정리

특성varletconst
스코프함수 스코프블록 스코프블록 스코프
재선언가능불가능불가능
재할당가능가능불가능
호이스팅undefined로 초기화TDZTDZ
선언과 동시에 초기화선택선택필수

참고 자료