호이스팅(Hoisting)의 개념
함수선언문과 함수표현식에서의 호이스팅 차이를 이해
var / let / const 변수 선언에서의 호이스팅
호이스팅 (Hoisting)
호이스팅이란 “끌어올리다"라는 의미의 hoist에 ing를 붙여 만든 동명사로, 함수 안에 있는 선언들을 모두 끌어올려서 해당 함수 유효 범위의 최상단에 선언하는 것을 말한다
- 자바스크립트 Parser가 함수 실행 전 해당 함수를 한 번 흝는다
- 함수 안에 존재하는 변수 / 함수선언에 대한 정보를 기억하고 있다가 (스코프에 등록) 실행시킨다
- 유효 범위 : 함수 블록 {} 안에서 유효
- 코드 실행 전 이미 변수선언 / 함수선언이 저장되어 있기 때문에 선언문보다 참조 / 호출이 먼저 나와도 오류 없이 동작한다
즉, 함수 내에서 아래쪽에 존재하는 필요한 값들을 위로 끌어올리는 것
- 자바스크립트 엔진이 실제로 끌어올리지는 않지만, 변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 고유의 특징이다
- 실제 메모리에서는 변화가 없다
호이스팅의 대상 (변수 호이스팅)
var 변수 / 함수의 선언만 호이스팅이 일어난다고 많이들 잘못 알고계신다. 자바 스크립트의 모든 선언에는 호이스팅이 일이난다. 모든 선언문은 런타임 이전 단계에서 먼저 실행되기 때문에 즉, 호이스팅이라는 용어가 ‘선언이 먼저 메모리에 저장, 끌어올려진다' 라는 의미이기 때문에 모든 선언은 호이스팅이 일어난다는 말이다
var vs let & const 차이점
- let, const, class를 이용한 선언문은 호이스팅이 발생하지 않는 것처럼 동작한다
- var 변수와는 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러 (ReferenceError)가 발생한다 (var는 undefined)
- let 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대 즉, TDZ(Temporal Dead Zone)에 빠지기 때문이다 (TDZ에 의한 제약)
- 선언과 함께 undefined로 초기화되어 저장되는 var랑은 다르게 let과 const는 초기화되지 않는 상태로 선언만 메모리에 저장되기 때문이다 (초기화 되지 않으면 변수를 참조할 수 없기 때문에 참조 에러가 발생)
- let과 const에도 호이스팅이 일어나기 때문에 에러를 일으키는 것
console.log(name)// undefined
var name = 'hello'
이 코드는 참조 에러가 나지 않고, undefined를 리턴한다. 이유는 자바스크립트가 호이스팅을 하면서 아래와 같은 방식으로 코드를 해석하기 때문이다
var name// undefined
console.log(name)
name = 'hello'
let foo = 1;
{
console.log(foo);
let foo = 2;
}
위 예시에서 알 수 있듯이, let 키워드에서도 상단으로 끌어올려진 것 같은 현상 (호이스팅)이 발생했기 때문에 에러가 발생한다. 에러가 난다고 호이스팅이 되지 않은 것이 아닌, 호이스팅이 발생했기 때문에 에러가 발생한 것
const x = 'outer scope';(function() {
console.log(x);
const x = 'inner scope';
}());
console.log(x)는 TDZ에 의해 ReferenceError를 발생하게 된다
변수 3단계
1단계: 선언 단계(Declaration phase)
- 변수를 실행 컨텍스트의 변수 객체에 등록한다
- 이 변수 객체는 스코프가 참조하는 대상이 된다
2단계: 초기화 단계(Initialization phase)
- 변수 객체에 등록된 변수를 위한 공간을 메모리에 확보한다
- 이 단계에서 변수는 undefined로 초기화 된다
3단계: 할당 단계(Assignment phase)
- undefined로 초기화된 변수에 실제 값을 할당한다
var 키워드로 선언한 변수는 선언 단계와 초기화 단계가 한번에 이뤄진다. 즉, 스코프에 변수를 등록(선언 단계)하고 메모리에 변수를 위한 공간을 확보한 후, undefined로 초기화한다. 따라서 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않는다. 다만 undefined를 반환한다. 이후 변수 할당문에 도달하면 비로소 값이 할당된다.
var 변수 호이스팅 예시
// 호이스팅 때문에 선언이 끌어올려져서 오류 안남.
console.log(text); // (선언 + 초기화 된 상태)
text = 'Voyage!'; // (선언 + 초기화 + 할당 된 상태)
var text;
console.log(text);
var 함수 호이스팅 예시
foo1(); // 함수 선언문에서는 호이스팅 일어난다.
foo2(); // 함수 표현식이라서 호이스팅 안된다.
function foo1() {
console.log('Hello');
}
var foo2 = function() {
console.log('world');
}
let/const 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 즉, 스코프에 변수를 등록(선언 단계)하지만 초기화 단계는 변수 선언문에 도달했을 때(코드 실행 후) 이뤄진다. 초기화 이전에 변수에 접근하려고 하면 참조 에러가 발생한다. 이는 아직 변수가 초기화되지 않았기 때문이다. 즉, 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문이다. 따라서 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없다. 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 ‘일시적 사각지대(Temporal Dead Zone; TDZ)’라고 부른다
TDZ 살펴보기
TDZ란 위에서 말한 일시적 사각지대 즉, 어휘적 바인딩이 실행되기 전까지 액세스할 수 없는 현상을 TDZ라고 한다
let/const 선언은 실행중인 실행 컨텍스트의 어휘적 환경(Lexical Environment)으로 범위가 지정된 변수를 정의한다. 변수는 그들의 어휘적 환경에 포함될 때 생성되지만, 어휘적 바인딩이 실행되기 전까지는 액세스할 수 없다
// const x를 실행하기 전에 x에 접근하면, TDZ에 의해 ReferenceError가 발생하게 된다.
console.log(x);
const x = 42;
// 위 코드 실행 이후에는 x에 접근할 수 있다.
console.log(x);
어휘적 바인딩에 의해 초기화되며 정의된 변수는 변수가 만들어질 때가 아닌, 값이 해당 초기화 구문 어휘적 바인딩이 실행될 때 값을 할당받는다
let/const 선언의 어휘적 바인딩에 초기화 구문이 없으면, 어휘적 바인딩을 실행할 때, 변수에 undefined가 할당된다
let x; // 이는 let x = undefined; 와 같다.
// const 키워드는 반드시 선언과 동시에 값을 할당해야 한다.
// const x; Uncaught SyntaxError: Missing initializer in const declaration
변수는 초기화 구문이 완전히 실행된 이후에 우변이 실행되고 그 결과 값이 선언된 변수에 할당된 후 초기화된다. 우변에서 let/const로 선언된 변수를 사용하는 경우, 우변은 변수를 읽으려고 시도하지만 초기화 구문이 아직 완전히 실행되지 않은 상태이므로, ReferenceError가 발생
let / const 호이스팅 예시
// 호이스팅 때문에 선언이 끌어올려져서 오류 안남.
console.log(text); // (선언 + 초기화 된 상태)
text = 'Voyage'; // (선언 + 초기화 + 할당 된 상태)
var text;
console.log(text);
const text; // 에러남. 주의! 애초에 const 키워드로 재할당 불가능! 그래서 선언과 동시에 할당해야함
결론
- var / let / const 모두 호이스팅 된다
- let /const 변수는 스코프에 진입할 때 변수가 만들어지고 TDZ가 생성, 변수가 실제 있는 위치에 도달할 때까지 액세스할 수 없는 것
- const는 선언과 동시에 초기화, 할당까지 이루어진다
- let / const 변수가 선언된 시점에서 제어흐름은 TDZ를 떠난 상태가 되며, 변수를 사용할 수 있게 된다
- let 대신 const를 사용하자 **let**으로 선언되어 있다면, 어디선가 이 변수가 바뀔지도 모른다는 생각을 가지고 있어야 하므로, 코드를 읽기가 어려워진다. 반면 **const**는 초기화, 선언, 할당까지 되어 있으니 변경되지 않을 것이다라는 확신으로 코드를 볼 수 있다. (물론 객체의 속성은 바뀔 수 있음)
출처 : 아래의 사이트들을 보면서 큰 공부 하였습니다
https://yceffort.kr/2020/05/var-let-const-hoisting
https://medium.com/korbit-engineering/let과-const는-호이스팅-될까-72fcf2fac365
https://hanamon.kr/javascript-호이스팅이란-hoisting/
https://gmlwjd9405.github.io/2019/04/22/javascript-hoisting.html
코어 자바스크립트 p.42
모던 자바스크립트 딥 다이브 p.42~49
'Front-End > JavaScript' 카테고리의 다른 글
[JavaScript] var / let / const 변수 (0) | 2022.03.09 |
---|---|
[JavaScript] 스코프 (Scope) (0) | 2022.03.07 |
[JavaScript] 데이터 타입 (Data Type) (0) | 2022.03.03 |
[JavaScript] 기본 문법 (0) | 2022.03.02 |
[JavaScript] DOM (Document Object Model) (0) | 2022.03.02 |