변수의 호이스팅, TDZ의 개념 바로알기

2022-03-12

호이스팅 (Hoisting) 이란 ?

JavaScript에서 호이스팅(hoisting)이란, 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리
할당하는 것을 의미합니다. -MDN-

함수 안에 있는 선언들을 모두 끌어올려서 해당 함수 유효 범위(실행 컨텍스트)의 최상단에 선언하는 것.


호이스팅의 대상

자바스크립트에서는 모든 선언을 호이스팅한다.

보통 호이스팅은 var 키워드로 선언된 변수에서만 일어난다고 생각하지만 let, const 에서도 호이스팅이 일어난다.


변수의 생성 단계를 알아보면서 이해해보자

변수는 3개의 단계를 거쳐 생성이 된다.


1. 선언단계

자바스크립트에서 변수의 선언은 var, const, let 키워드로 할 수 있으며, ES6에서 const와 let이 추가되었다.

선언단계에서는 변수를 실행 컨텍스트의 변수 객체에 등록하고 자바스크립트 엔진에 변수의 존재를 알린다.

이 변수 객체는 스코프가 참조하는 대상이 된다.


2. 초기화 단계

값을 저장하기 위한 메모리 공간을 확보하고 이 단계에서 변수에 undefined를 할당해 초기화한다.

초기화 단계는 어느 선언 키워드를 사용하느냐에 따라 다르게 나타난다.


var 키워드를 사용한 선언 방식의 경우,

var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한꺼번에 일어난다.

초기화 단계에서 undefined로 초기화되기 때문에 변수를 선언하기 전에 변수에 접근해도 에러가 일어나지 않는다.

접근이 가능해진 var 키워드로 선언한 변수는 실행 컨텍스트의 최상위로 이동하게 된다.

코드를 통해 알아보자.


console.log(age); // undefined

var age = 27;

console.log(age); // 27

위 코드에서 확인 할 수 있듯이, var 키워드로 선언한 변수는 선언 단계와 초기화 단계가 한번에 일어나기 때문에

age를 console.log로 확인해보면 undefined로 나오게 되고 이후 할당단계를 거쳐 27이라는 값이 찍히게 된다.

위의 코드는 아래와 같은 의미를 가진다.


var age;

console.log(age); // undefined

age = 27;

console.log(age); // 27

var 키워드로 선언한 변수가 호이스팅이 되어 실행 컨텍스트의 최상단으로 이동했고, 이 때 선언과 초기화가 동시에 일어났기 때문에

undefined 라는 값이 나오게 되는 것이다.


반면,

let, const 키워드로 선언된 변수는 var 키워드로 선언된 변수와 달리 선언 단계와 초기화 단계가 분리되어서 이루어진다.

초기화 이전에 변수에 접근하려고 하면 참조 에러가 발생한다.

이는 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문이다.


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

코드를 통해 알아보자.

const 변수

age; // ReferenceError
const age = 27;

const 변수는 초기화 단계 이전까지 TDZ에 있다.


let 변수

let 키워드로 선언한 변수도 const와 마찬가지로 초기화 단계 이전까지 TDZ에 있다.


age; // ReferenceError
console.log(age);

3. 할당 단계

변수에 값을 할당 할 때에는 할당 연산자(=)를 사용해서 이루어지며 모든 선언 키워드의 할당은 런타임 과정에서 이루어진다.

var 키워드 변수는 실행 컨텍스트의 (실행 단계) 에서 선언과 초기화 단계가 이루어지고, 런타임 과정에서 할당이 이루어진다.

let, const 키워드 변수는 실행 컨텍스트의 (실행 단계) 에서 선언 단계만 이루어지고, 런타임 과정에서 초기화 단계를 거쳐 메모리 공간을

확보하여 할당 할 값(undefined)을 저장한다.

이후 할당 단계에서 undefined 으로 초기화 되어있는 값을 할당된 값으로 교체해준다.


함수 호이스팅

변수의 호이스팅에 대해 알아보았다. 이제 함수의 호이스팅에 대해 알아보자

함수선언문으로 함수를 정의한 경우


getAge(27); // 정상동작

function getAge(age) {
  console.log(age); //27
}

getAge(27); // 정상동작

함수선언문은 TDZ에 영향을 받지 않는다. 현재 스코프에서 호이스팅 된다.

함수 표현식, 화살표 함수로 함수를 정의한 경우


getAge(27);

var getAge = (age) => {
  // TypeError: getAge is not a function
  console.log(age);
};

var getAge = function (age) {
  // TypeError: getAge is not a function
  console.log(age);
};

getAge(27);

반면 함수표현식과 화살표 함수로 함수를 정의하기 이전에 함수를 호출한 경우에는 에러가 발생한다.

호이스팅이 일어나지만 var로 선언했기 때문에 getAge에는 undefined가 초기화 되어 있는 것이다

위 함수를 let, const 로 선언했을 경우에는 함수를 정의하기 전에 호출했다는 에러가 발생하게 된다 !


var, let, const의 차이

위에서 확인했듯이 var 키워드로 함수를 선언하는 경우에는 문제점이 많다.

  • 변수 중복 선언 가능하다.
  • 함수 레벨 스코프이기 때문에 함수가 아닌 곳에서 선언한 변수는 모두 전역 변수로 취급한다.
  • 변수의 할당 단계를 거치기 이전에는 항상 undefined를 가진다.

이러한 var의 문제점을 해결하기위해 es6에서 let과 const가 탄생했다.


변수의 중복 선언 제한

let 키워드와 const 키워드는 같은 스코프 내에서 중복 선언을 허용하지 않는다.  중복 선언하면 문법에러SyntaxError가 발생한다.

function getAge() {
  let age = 27;
  let age = 26;
  // 중복 선언으로 인해 에러 발생
  return age;
}

let과 const의 차이점이라면

const는 변수의 선언과함께 초기화를 하지 않으면 에러가 발생한다 .


let age; // undefined
console.log(age);

const age; // Error
console.log(age);

const age = 27;
console.log(age); // 27

블록 레벨 스코프

var키워드는 함수 레벨 스코프이다.
하지만 let키워드와 const 키워드로 선언한 변수는 모든 코드 블록(함수,if문, for문, while문, try/catch문 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.


let age = 27; // 전역 변수

{
  let age = 30; // 지역 변수
  let name = 'song'; // 지역 변수
}

console.log(age); // 27
console.log(name); // ReferenceError: name is not defined

블록레벨 스코프이기 때문에 블록 안의 스코프에서 선언된 함수는 블록 밖에서 접근할 수 없다

그러므로 name은 선언되지 않았다고 인식이 되는 것이다

하지만 변수들을 var로 선언했다면 모든 변수들이 전역변수로 호이스팅되어 블록 내에서 선언한 변수들의 값이 출력이 된다.


호이스팅

var와 let, const선언의 차이 중 하나는 let, const가 TDZ에 의해 제약을 받는다는 것이다.

즉, 변수가 초기화되기 전에 접근하려 하면, var처럼 undefined를 반환하지 않고, ReferenceError가 발생한다.


let age = 27;

function getAge() {
  console.log(age); // ReferenceError
  let age = 28;
}

위의 코드에서 let으로 선언한 변수에서 호이스팅이 일어난다는 것을 알 수 있다.

호이스팅이 되지 않는다면 console.log 에서는 전역에서 선언한 age인 27이 출력이 되어야 하지만

호이스팅이 되었기 때문에 ReferenceError를 발생시키는 것이다.

undefined를 반환하는 var와는 달리, 초기화되기 전에 접근하면 에러가 발생한다.


const의 재할당 금지


const age = 27;
age = 28; // TypeError: Assignment to constant variable.

const로 선언한 변수는 재할당이 금지되어있다. 하지만 let으로 선언한 변수는 재할당이 가능하다


const 변수에 객체를 할당한 경우

const 변수의 타입이 객체인 경우, 객체에 대한 참조를 변경하지 못한다는 것을 의미한다. 하지만 객체 내부의 값은 변경이 가능하다.


const person = {
  // 객체를 상수로 선언
  age: 27,
};

person = {
  // 객체가 담겨있는 참조는 변경 불가능 때문에 Error
  age: 26,
};

person.age = 26; // 객체가 가지고 있는 value는 변경이 가능

console.log(person.age);

정리

변수를 선언할 때 var 키워드로 선언하기 보다는 let, const 로 선언하도록 하자

변하지 않는 값이 필요하다면 상수를 선언할 const 키워드를 사용하고 재할당이 필요하다면 let 키워드를 사용하도록 하자.



Reference

https://ui.toast.com/weekly-pick/ko_20191014

https://wonism.github.io/is-let-hoisted/

© 2024 SongChangYeop All rights reserved