hoisting?
hoist의 사전적 의미는 '끌어 올리기' 라는 뜻이다.
그래서 자바스크립트 안에서의 호이스팅의 의미는 변수나 함수의 호출 코드보다, 선언 코드가 아랫쪽에 있음에도 불구하고 에러가 발생하지 않고, 마치 선언 코드가 호출 코드보다 더 위에 선언된 것과 같이 동작하는 특성이라고 한다.
처음부터 이 말을 이해하려고 하면 잘 안 될 것이다. 우선 자바스크립트에서 변수를 선언하는 방법 중, let과 var의 차이점부터 짚고 넘어가야 호이스팅의 개념을 더 정확하게 이해할 수 있다.
let과 var의 차이: 태초에 var는 문제가 많았다..
옛날 옛적에는 지금의 우리가 널리 쓰고 있는 let이 아닌, var만 사용했다고 한다. 그러다 ECMA-262 기술 규격에 따라 정의하고 있는 표준화된 스크립트 프로그래밍 언어인 ECMAScript가 2015년 6월에 ES6로 업그레이드를 하는데, 이때 let이 처음 나오기 시작한 것이다. 즉, var 사용 시 문제가 있었기 때문에 업그레이드를 한 것이다.
var 사용에 대체 어떤 문제가 있었길래?
값을 선언도 하기 전에 할당할 수 있음
아래와 같이 코드를 입력하고, 코드가 실행되기 전에 자바스크립트는 먼저 선언된 변수와 함수를 가져가서 메모리에 기억을 해둔다. 즉, 자바스크립트 라는 선생님이 수업을 시작하기 전에(= 코드를 실행하기 전에) 어떤 학생들이 있는지(= 어떤 변수들이 있는지) 조사(= 출석체크) 를 한다고 보면 된다. 그러다, 누가 ella 라는 변수를 부르면 그 변수가 나오는 것이다. 즉, 함수가 실행되기 전에 안에 있는 변수들을 범위의 최상단으로 올려 보내는 것이다.
var age = 10
var name = "Ella"
var student = true
console.log(age)
JavaScript
복사
코드를 통해 한번 더 설명해 보겠다.
// var로 변수 ella를 선언하고, 변수의 값을 1로 할당
var ella = 1
console.log(ella) // 1
JavaScript
복사
첫 번째 코드는 문제 없이 1이 잘 출력된다.
console.log(ella) // undefined
var ella = 1
console.log(ella) // 1
JavaScript
복사
코드는 아래에서 위로 흘러가기 때문에 두 번째 코드는 ella가 선언되기도 전에 ella를 써서 에러가 날 것 같지만, 우리의 자바스크립트 선생님은 에러를 내지 않고 undefined와 1을 출력한다.
왜? 바로 호이스팅 때문이다.
우리 위대하신 자바스크립트 선생님은 코드 첫 번째 줄에서부터 ella가 없으면 바로 에러를 내는게 아니라, undefined를 주면서 아직 할당되지 않았음을 이야기하고, 뒤이어 두 번째 줄에 ella가 있음을 발견한 후 1을 출력한다. 즉, 출석을 하지 않았다고 바로 결석처리를 한게 아니라 출석 보류 처리를 한 것이라고 보면 된다.
어떻게 이런 것이 가능할까?
호이스팅을 하면 변수의 선언과 (undefinded 로) 초기화를 같이 시켜버리고, 값 할당은 나중에 ella가 있는 해당 줄에 가서 한다.
console.log(ella) // undefined
ella = 1
var ella
console.log(ella) // 1
JavaScript
복사
심지어 선언도 하지 않고, 선언을 할당 뒤에 해버려도 undefined와 1이 나온다.
전역변수와 지역변수의 개념이 확실하지 않음
여기서 잠깐, 전역변수와 지역변수의 차이를 모른다면 먼저 알고 가자.
var ella = 2
JavaScript
복사
위와 같이 전역변수는 어떠한 범위도 없이 블락 { } 밖에서 선언을 한 어디서든 쓰일 수 있는 변수다.
var ella = "yes"; // 전역 변수 ella
function attend() {
var chloe = "..."; // 지역 변수 chloe
}
console.log(chloe); // ReferenceError: chloe is not defined
// { } 지역을 벗어나면 에러가 남
JavaScript
복사
위와 같이 지역변수는 function이나 for문 등의 블락 { } 안에서 선언된 변수이며 블락 안에서만 쓸 수 있다.
두 변수의 차이를 잘 이해했다면 아래의 for문 예시 코드를 다시 보자.
for(var i = 1; i < 5; i++) {
console.log(i)
}
console.log(i) // 1 2 3 4 5: 에러가 나지 않음
JavaScript
복사
여기서 var i 는 for문 { } 안에서 선언이 되기 때문에 블락 밖에서 i를 출력하려고 하면 에러가 나야 하는 것이 맞다. 하지만 에러가 나지 않는다. 즉, var를 사용하면 함수(function)만 지역변수로 호이스팅이 되고, 나머지(for문, if문 등)는 다 전역변수로 올려버리는 것이다.
같은 이름의 변수가 두 개 이상이 될 수 있음
var ella = 1
console.log(ella) // 1
var ella = 2
console.log(ella) // 2
JavaScript
복사
변수의 이름은 절대로 중복이 되지 말아야 하는데, 위와 같은 상황이라면 나와 같은 주민등록번호가 같은 사람이 두 명이 있다는 뜻이다. 위와 같이 코드를 짜면 특히 큰 프로젝트일수록 굉장한 혼란이 올 것이다.
위 이유들로 인해 var의 문제점들을 보완할 let을 만들었다.
위의 코드에서 var을 let으로 바꿔보면 정상적으로(?) 에러가 잘 난다. ella 라는 변수가 이미 선언이 되었으니, 중복으로 선언할 수 없다는 뜻이다.
let ella = 1
console.log(ella)
let ella = 2
console.log(ella) // SyntaxError: Identifier 'ella' has already been declared
JavaScript
복사
for(let i = 1; i < 5; i++) {
console.log(i)
}
console.log(i) // ReferenceError: i is not defined
JavaScript
복사
마찬가지로 위에 작성했던 for문 에서도 let으로 변경하면 i가 정의되지 않았다는 정상적인 에러가 난다. i는 지역변수로 선언이 되었는데, 블록 밖에 있는 console.log(i)는 블록 안에 있는 지역변수에 접근할 수 없다는 뜻이다.
console.log(ella)
let ella = 1
console.log(ella) // ReferenceError: Cannot access 'ella' before initialization
JavaScript
복사
마지막으로, 처음에 작성했던 코드 안에서도 var 대신 let을 사용하면 정상적인 에러가 난다. 초기화가 되기 전에 ella 에 접근할 수 없다는 의미다.
let을 사용하는 순간, 모든 문제가 해결이 되었다!
let은 이 문제를 어떻게 해결한 것일까? 사실, let도 호이스팅이 된다.
console.log(ella)
// TDZ: 아직 ella가 선언되지 않았으므로 일시적으로 죽은 zone임
let ella = 1
// 뒤늦게 ella 선언함
console.log(ella)
// ReferenceError: Cannot access 'ella' before initialization: 그래도 에러남
JavaScript
복사
하지만 let이라는 개념을 만들면서 TDZ(Temporal Death Zone)이라는 개념도 함께 만들었는데, 이미 코드 첫 번째 줄에서 ella가 호이스팅이 된 것은 알지만, ella 선언문이 나오기 전까지는 ella에 접근할 수 없다는 의미 == 즉 일시적으로 죽은 zone 이다. ella가 초기화(initialization)가 될 때까지는 아무리 ella에 접근해도 실제로 선언되고 할당되는 라인 전까지는 ella를 쓸 수 없다.
따라서 아래와 같이 코드를 작성해야 한다.
let ella = 1 // ella 선언함
console.log(ella) // 1이 출력됨
JavaScript
복사
그래서 let과 const에서는 어떻게 호이스팅 되는데?
function a () {
for(let i = 0; i < 10; i++){
}
return i;
}
console.log(a()) // ReferenceError: i is not defined: 블록 밖에서 return 하기 때문
JavaScript
복사
우선, var는 function scope (= 함수 내에서 변수가 접근할 수 있는 영역), let과 const는 block scope (= 블록 내에서 변수가 접근할 수 있는 영역)이다. 따라서 위의 코드에서 i는 for문 블록 안에서만 접근이 가능하기 때문에 return을 하면 참조가 불가능하다는 에러가 난다.
let a = 1
if(true) {
console.log(a)
// Cannot access 'a' before initialization: 호이스팅되어 참조를 할 수 있게 됨
let a = 2
}
JavaScript
복사
다시 한번 말하지만, 호이스팅은 블록 스코프나 함수 스코프 내의 최상단으로 가는 것을 말하는데, console.log(a)로 a를 출력할 때도 호이스팅 되어 참조를 할 수 있게 된다. 하지만 이때는 선언만 되고 초기화가 되지 않아서 에러가 난다. 선언한 변수가 호이스팅이 일어나긴 했지만, let과 cost는 선언과 초기화가 동시에 일어나지 않았기 때문에 에러가 발생한 것이다.
즉, 호이스팅이 일어나긴 난다는 거다.
- var: 선언 및 초기화 → 할당
- let, const: 선언 → TDZ → 초기화 → 할당
왜 에러가 뜰까?
보통 자바스크립트 엔진은 undefined로 초기화가 되는데, 위와 같이 var는 선언과 동시에 일어나나, let과 const는 선언과 TDZ가 중간에 막고 있어서 선언과 초기화가 동시에 일어나지 않기에 선언은 되었으나 초기화가 되지 않았다는 에러가 난다.
따라서, 아래와 같이 작성하면 도출하고자 하는 값이 정상적으로 나온다.
let a = 1
if(true) {
let a = 2
console.log(a) // 2
}
JavaScript
복사
결론
var 쓰지 말고, let 쓰자!