티스토리 뷰
호이스팅에 대해서는 옛날에 퍼블리셔 수업에서 제이쿼리를 배울 때 함수를 어디에다 선언해도 사용할 수 있도록 선언을 맨위로 끌어올려주는 기술이라고 배웠던 기억이 난다. 지금까지 그정도의 개념만 가지고 코드를 작성하는데에 문제는 없었지만 자바스크립트에 대해 제대로 공부하고 있으니 자바스크립에서의 호이스팅에 대해서도 자세하게 알아보자.
1️⃣ 호이스팅이란?
자바스크립트는 ES6에서 도입된 let, const를 포함한 모든 선언(var, let, const, function, function*, class)을 호이스팅한다.
여기서 호이스팅이란 무엇일까? 호이스팅(hoisting)에 대한 다양한 정의를 찾아보았다.
"인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미한다." - MDN
"var 선언문이나 function 선언문 등을 해당 스코프의 가장 선두로 옮긴 것처럼 동작하는 특성을 말한다." - poiemaweb
"자바스크립트 엔진은 JS코드를 실행할 때 전역 실행 컨텍스트(global execution context)를 생성한다. 전역 실행 컨텍스트는 생성(Creation)과 실행(Execution), 두 단계로 이루어지는데 생성 단계에서 자바스크립트 엔진은 변수 및 함수 선언을 코드 맨 위로 이동시킨다. 이것을 호이스팅이라고 한다." - javascripttutorial
정리해보면,
자바스크립트 엔진은 코드를 실행하기 전, 전역 실행 컨텍스트를 생성하여 모든 선언(var, let, const, function, function*, class)을 해당 스코프에 등록한다. 이 이후 코드가 실행되기 때문에 var 선언문, 함수 선언문의 경우 선언이 코드의 최상단으로 끌어올려진 것처럼 동작하고 이것을 호이스팅이라고 한다.
호이스팅은 변수 선언, 함수 선언을 실제로 코드 최상단으로 끌어올리는 것이 아니라 코드가 실행하기 전 변수 선언, 함수 선언을 해당 스코프의 가장 선두로 끌어올린 것처럼 동작하는 특성을 말한다.
실제로 자바스크립트의 모든 선언(var, let, const, function, function*, class)은 호이스팅이 일어난다.
하지만 왜 var 선언문, 함수 선언문만 호이스팅이 가능한 것처럼 보이는지에 대해 알아보자.
2️⃣ 변수 호이스팅
변수 호이스팅은 변수 생성 및 초기화와 할당이 분리되어 진행된다.
호이스팅된 변수는 undefined로 초기화 되고 실제값의 할당은 할당문에서 이루어진다.
💡 변수 생성 단계
변수는 아래와 같은 세 단계에 걸쳐서 생성된다.
1) 선언 단계(Declaration phase)
: 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다. 이 변수 객체는 스코프가 참조하는 대상이 된다.
2) 초기화 단계(Initialization phase)
: 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다. 이 단계에서 변수는 undefined로 초기화된다.
3) 할당 단계(Assignment phase)
: undefined로 초기화된 변수에 실제 값을 할당한다.
var 키워드로 선언된 변수
var 키워드로 선언된 변수는 스코프의 선두에서 선언 단계와 초기화 단계가 함께 이루어진다. 즉, var 키워드로 변수를 선언하면 스코프에 변수를 등록하고 메모리에 변수를 위한 공간을 확보한 후 undefined로 초기화 한다는 것이다.
그렇기 때문에 var 키워드로 선언한 변수는 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하니까 에러가 발생하지 않고 undefined를 반환한다. 이후 변수 할당문이 실행되면 변수에 값이 할당된다.
// 선언 단계, 초기화 단계 실행
console.log(foo); // undefined
var foo;
console.log(foo); // undefined
foo = 1; // 할당 단계 실행
console.log(foo); // 1
이러한 현상이 변수 호이스팅이다.
let 키워드로 선언된 변수
let 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 즉, 스코프의 선두에서 선언 단계만 실행되어 스코프에 변수를 등록하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어진다.
그렇기 때문에 let 키워드로 선언한 변수는 초기화 이전(변수 선언문 이전)에 변수에 접근하려고 하면 참조 에러(Reference Error)가 발생한다. 참조 에러는 변수가 초기화되지 않았기 때문에 즉, 변수를 위한 메모리 공간이 확보되지 않았기 때문에 일어난다.
따라서 let으로 선언한 변수는 스코프의 시작 지점부터 초기화의 사작 지점까지는 변수를 참조할 수 없고 이 구간을 "일시적 사각지대(Temporal Dead Zone; TDZ)"라고 부른다. 이와 같은 현상때문에 let으로 선언한 변수는 호이스팅 되지 않는다고 보여진다.
// 선언 단계 실행
console.log(foo); // ReferenceError: foo is not defined
let foo; // 초기화 단계 실행
console.log(foo); // undefined
foo = 1; // 할당 단계 실행
console.log(foo); // 1
let, const로 선언된 변수는 호이스팅되지 않는다고 잘못 알고 있는 경우도 많은데 위에서도 언급했듯이 호이스팅 되지 않는다고 보여지는 것 뿐, 자바스크립트에서 모든 선언은 호이스팅이 일어난다.
let으로 선언한 변수에서도 호이스팅이 일어나는지 확인해보고 싶다면, 빈 html 파일에 아래와 같이 JS 코드만 입력하고 브라우저로 실행해보자. 그러면 ReferenceError: Cannot access 'number' before initialization 라는 에러가 발생한다.
console.log(number);
let number = 10;
이번에는 아래와 같이 코드를 바꿔서 콘솔창을 확인해보면, ReferenceError: number2 is not defined 라는 에러가 발생하는 것을 볼 수 있다.
console.log(number2);
let number = 10;
전자의 경우는 number라는 변수의 초기화 전에는 참조할 수 없다는 것이고, 후자의 경우는 number2라는 변수 자체가 정의되지 않았기 때문에 number2가 무엇인지 모른다는 의미이다.
여기서 알 수 있는 점은 전자의 경우 정의되지 않음이 아닌 것을 보아 number라는 변수를 인식했다는 것이다. 이 인식을 했다는 것이 number 변수가 호이스팅이 되었다는 증거이다.
또 다른 예제를 보자면, 아래 코드의 경우 4번째 줄의 console.log에서 전역 변수 foo의 값이 출력될 것 같지만 let은 블록 레벨 스코프(자신을 선언한 블록과 모든 하위 블록)를 가지기 때문에 지역 변수 foo가 해당 스코프에서 호이스팅되고 블록의 선두부터 초기화가 이루어지는 지점까지 일시적 사각지대(TDZ)에 빠진다.
let foo = 1; // 전역 변수
{
console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
let foo = 2; // 지역 변수
}
그래서 4번째 줄의 console.log에서 전역변수 foo를 출력하는 것이 아니라 참조 에러를 발생시킨다.
const 키워드로 선언된 변수
const 키워드로 선언된 변수는 반드시 선언과 할당이 동시에 이루어져야 한다. const는 상수를 위해 사용하는 키워드이기 때문이다.
이외의 특징은 대부분 let과 동일하다. 호이스팅에 대한 부분도 let과 동일하게 생각하면 된다.
3️⃣ 함수 호이스팅
함수는 변수와는 메모리에 저장되는 방식이 다르다. 함수는 전체 함수에 대한 참조와 함께 저장되기 때문에 함수 선언 이전에 함수를 호출이 가능하다. (함수 선언문의 경우)
💡 함수 정의 방법
함수를 정의하는 방법은 아래와 같은 방법들이 있다.
함수 선언문
function square(number) {
return number * number;
}
함수 표현식
var square = function(number) {
return number * number;
};
Function 생성자 함수 (이 방법은 일반적으로 사용하지 않는다)
var square = new Function('number', 'return number * number');
console.log(square(10));
화살표 함수
var square = (number) => number * number;
각 방법에 대한 자세한 내용에 대해서는 따로 정리하기로 하자.
2023.02.20 - [👩💻 개발공부/🟨 자바스크립트 | JavaScript] - [JS] 함수 표현식, 함수 선언식, 화살표 함수
함수 선언문으로 정의된 함수
위 세 가지의 함수 정의 방법들은 동작 방식에 약간의 차이가 있다.
아래 코드를 보면 함수 선언문으로 함수가 정의되기 이전에 함수의 호출이 가능하다.
var res = square(5);
function square(number) {
return number * number;
}
함수 선언문으로 정의된 함수는 자바스크립트 엔진이 스크립트가 로딩되는 시점에 바로 초기화하고 이를 VO(variable object, 변수 객체)에 저장한다. 즉, 함수의 선언, 초기화, 할당이 한번에 이루어진다.
따라서 함수 선언의 위치와는 상관없이 어디서든 호출이 가능하다.
함수 표현식으로 정의된 함수
아래 코드를 실행하면 함수 선언문과 달리 TypeError가 발생한다.
var res = square(5); // TypeError: square is not a function
var square = function(number) {
return number * number;
}
이는 함수 표현식의 경우 함수 호이스팅이 아니라 변수 호이스팅이 발생하기 때문이다. 전역 실행 컨텍스트의 생성 단계에서 자바스크립트 엔진은 square 변수를 메모리에 생성하고 해당 값을 undefined로 초기화한다.
그리고 다음 코드를 실행할 때 square는 undefined이므로 함수가 아니다.
square 변수는 전역 실행 컨텍스트의 실행 단계 중에만 익명 함수에 할당된다.
즉, 함수 표현식은 함수 선언문과 달리 스크립트 로딩 시점에서 VO에 함수를 할당하지 않고 runtime에 해석되고 실행된다.
화살표 함수로 정의된 함수
화살표 함수는 함수 표현식으로 함수를 정의하기 위한 syntactic sugar이기 때문에 함수 표현식 예제와 동일한 오류를 발생시킨다.
💡 Syntactic sugar
더 쉽게 읽거나 표현하도록 설계된 프로그래밍 언어 내의 구문
4️⃣ 정리
호이스팅이란 코드가 실행되기 전에 함수, 변수 또는 클래스의 선언을 메모리에 미리 저장하는 것이라고 볼 수 있다.
자바스크립트의 모든 선언(var, let, const, function, function*, class)은 호이스팅이 일어난다.
그 중 var 선언, 함수 선언문의 경우 호이스팅이 일어나는 것처럼 동작한다.
반대로 let, const, class, 함수 표현식의 경우 호이스팅이 일어나지 않는 것처럼 동작(에러 발생)한다. 하지만 호이스팅이 되었기 때문에 에러가 발생한 것이다.
초기화를 제외한 선언만 호이스팅의 대상이다. 참고 링크
참고 자료
🔗 [poiemaweb] let, const와 블록 레벨 스코프
🔗 [javascripttutorial] JavaScript Hoisting
🔗 [dev.to] JavaScript Visualized: Hoisting
🔗 [freecodecamp] Hoisting in JavaScript with let and const – and How it Differs from var
'개발공부 > 🟨 JavaScript' 카테고리의 다른 글
[JS] 프로토타입 (Prototype) (0) | 2023.02.19 |
---|---|
[JS] 자바스크립트 비동기 처리 (0) | 2023.01.29 |
[JS] 클로저 (Closure) (0) | 2023.01.09 |
[JS] 실행 컨텍스트 (Execute context) (0) | 2023.01.04 |
[JS] 이벤트 루프 (Event Loop) (0) | 2022.09.30 |
프론트엔드 개발자 삐롱히의 개발 & 공부 기록 블로그