평소에 헷갈렸던 호이스팅 개념을 이해하기 위해 공부한 내용을 정리한 글입니다. 잘못된 내용이 있다면 알려주시면 감사하겠습니다 :)
var과 let의 호이스팅을 각각 살펴보자.
코드 실행 전에 변수 선언이 스코프의 상단으로 끌어올려지는 것 같은 현상
var a = 'hello';
스크립트가 실행되면 자스 엔진은 코드 실행에 필요한 정보들을 담고 있는 실행 컨텍스트를 생성한 후 콜 스택에 push 한다. 이때 실행 컨텍스트 내의 환경 레코드에는 모든 지역변수(변수, 함수선언)가 프로퍼티로 저장되어 있는데, var 로 선언한 변수의 경우에는 코드 평가 단계에서 변수를 선언과 동시에 undefined로 초기화를 해준다.
GlobalExecutionContext = {
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
a: undefined,
}
outer: <null>
ThisBinding: <Global Object>
}
}
이처럼 코드 평가 단계에서 a 변수가 undefined로 초기화되어 있다.
호이스팅이 된 변수에 접근을 해보자.
console.log(a); // undefined
var a = 'hello';
console.log(a) // hello
이처럼 변수 선언 코드 이전에 변수에 접근이 가능한 이유는, 코드 평가 단계에서 선언과 동시에 undefined로 초기화했기 때문에 코드 실행 단계에서 변수 선언 이전에 변수 접근이 가능해진 것이다.
따라서 선언이 스코프의 상단으로 끌어올려진 것처럼, 선언 이전에 참조할 수 있는 이유는 코드 실행 이전에 초기화가 일어났기 때문이다.
참고로 a에 'hello'를 할당하고 있는데, 변수의 값 할당은 코드 실행단계에서 일어나기 때문에 변수 선언 이전에 접근하면 할당 이전 값인 초기화 값 undefined 가 출력된다.
let a = 'hello';
결론적으로 let과 const로 선언한 변수도 선언이기 때문에 호이스팅이 발생한다. 하지만 아래에서 볼 수 있듯이 선언 이전에 참조하면 reference Error가 발생하는 것을 볼 수 있다.
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 'hello';
에러로 인해 호이스팅이 발생하지 않는 것처럼 느껴지지만 그렇지 않다. 왜 이런 에러가 발생하는 지 레코드와 함께 let의 호이스팅을 이해해보자.
let으로 변수를 선언하면 아래와 같은 환경 레코드가 구성된다.
GlobalExecutionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
a: < uninitialized >,
}
outer: <null>
ThisBinding: <Global Object>
}
}
let으로 변수를 선언한 경우에는 var와는 달리 초기화가 되어 있지 않다는 것을 알 수 있다. 이는 var와는 달리 선언과 초기화가 동시에 일어나지 않고 분리되어 발생하기 때문이다.
때문에 아직 초기화되지 않은 변수에 접근하려니 reference error가 발생하는 것이다.
이처럼 변수 선언 라인 전까지 (초기화 이전까지) 변수에 접근할 수 없는 이른바 사각지대 TDZ (Temporal Dead Zone) 가 형성되기 때문에 호이스팅이 발생하지 않는 것처럼 보이게 된다.
사각지대는 코드의 작성 순서(위치)가 아닌 코드의 실행 순서(시간) 에 따라 형성되기 때문에 아래와 같이 변수에 접근하는 함수 선언 코드가 변수 초기화 코드보다 앞서도, 초기화가 끝난 다음에 함수를 호출하게 되면 에러가 발생하지 않는다.
{
// 변수 a의 TDZ 시작
const func = () => console.log(a);
// TDZ 안에서 변수 a에 접근하게 되면 ReferenceError 발생.
let a = 'hello'; // 변수 a의 TDZ 종료
func(); // TDZ 밖에서의 호출로 에러가 발생하지 않는다.
}
코드의 실행 단계에서 let을 만나면 undefined로 초기화를 해준다. 고로 선언라인 이전까진 TDZ이다.
console.log(a); // Reference Error
let a;
console.log(a); // undefined
엄격 모드가 아닌 경우에는 선언 없이 할당한 변수는 var로 선언한 것과 같아지며, 전역 객체 window의 프로퍼티가 되어 어느 곳에서든 접근이 가능해진다. (자스의 객체가 아님!)
반면에 let으로 선언한 변수는 전역객체의 프로퍼티로 추가되지 않는다.
a = 'hello';
console.log(a); // hello;
console.log(window.hasOwnProperty('a')) // true
var b = 'hi';
console.log(window.hasOwnProperty('b')) // true
let c = 'bye';
console.log(window.hasOwnProperty('c')) // false
엄격모드에서는 선언없이 할당할 경우 Reference Error가 발생한다.
'use strict';
a = 'hello';
console.log(a); // ReferenceError: a is not defined
그렇다면 선언 없이 할당할 경우 호이스팅이 발생할까?
console.log(a); // Reference Error
a = 'hello'; // 선언 없이 할당
console.log(a); // undefined
a = 'hello';
var a; // 선언
호이스팅은 선언만을 대상으로 일어나기 때문에 선언 없이 초기화만 있는 경우에는 호이스팅이 일어나지 않는다.
따라서 아래와 같은 예는 선언이 없으니 호이스팅이 발생하지 않은 것이다.
a = 'hello';
console.log(a); // hello
MDN - let
MDN - var
MDN - Hoisting
What happen when we directly assign the variable without declaring it in JavaScript ?