(자알쓰) Hoisting

자알쓰란?

바스크립트 자. (잘 쓰자는 의미도 담겨있다.)
자바스크립트라는 언어 자체는 내 기준에서는 설계 상 미스가 참 많다.
함수 단위의 스코프, 호이스팅, 동적 타입 등등
자바와 같은 깐깐(?)한 언어를 배우고 바라본 자스는 허점 투성이처럼 보였다.
애초에 자바스크립트는 어떠한 프로그램을 만들기 위해서 탄생했다기 보다는
웹 페이지에 입력값에 대한 유효성 검사(데이터가 공란인지 아닌지 등등)와 같은
페이지의 동적 제어가 주된 목적 + 짧은 개발 기간(넷 스케이프 사의 새로운 브라우저에 탑재 예정) 때문에
설계 상에 미스가 있을 수 밖에 없다고 나는 생각된다.
일종의 안전 장치가 없어서 개발자가 일일이 구현해주고, 신경써야 하는 느낌이었다.
그렇다고 해서 자바스크립트를 극혐하거나 그런 것은 아니고 매우 사랑한다.
또한 그 허점을 아는 사람은 허점을 보완해서 요리조리 피해서 잘 쓰겠지만…
잘 모르는 부분들은 잘못 써도 동작이 잘 되기 마련이다.
이는 지금 당장에는 큰 문제가 안 될지 모르겠지만, 추후에 대규모 웹 어플리케이션을 만들거나
직면할 문제로부터 미리 해방시키기 위해 처음부터 좋은 습관을 들여가는 것이 좋다고 생각한다.
그 두 번째 시리즈는 Hoisting(호이스팅)을 주제로 진행하겠다.

Hoisting? 호이스팅?

이 처음보는 단어는 뭐지 싶을 수도 있다.

Hoisting
끌어 올리기; 들어올려 나르기.

말 그대로 끌어올리는 것이다. 무엇을? 코드를!

1
console.log(qwer); // Uncaught ReferenceError: qwer is not defined

누구나 예상한 결과일 것이다.
qwer이란 녀석은 선언된 적이 없으니 사용도 불가능한 것이다.
하지만 아래 예제를 보자.

1
2
3
4
5
6
7
8
9
10
console.log(asdf); // undefined, 오류가 나질 않는다. 즉 프로그램이 뻗질 않는다.
console.log(qwer); // undefined
console.log(zxcv); // function zxcv() { console.log(456); }
var asdf = 'asdf';
var qwer = function() {
console.log(111);
};
function zxcv() {
console.log(456);
}

전통적인 C언어나 자바와 같은 프로그래밍 언어로 개발하던 사람들이 이해하지 못하는 부분 중 하나가 바로 이거다.
내가 선언하기 전에 사용된 변수, 즉 없는 변수인데 왜 갑자기 툭 튀어나와서
undefined를 내뱉고 프로그램이 죽지 않는 거지?
바로 호이스팅이 일어났기 때문이다.
위의 코드는 아래와 같이 변환된다고 보면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
var asdf;
var qwer;
function zxcv() {
console.log(456);
}
console.log(asdf);
console.log(qwer);
console.log(zxcv);
asdf = 'asdf';
qwer = function() {
console.log(111);
};

함수 선언문에서는 변수 없이 함수를 선언한 것이므로 전체 코드가 호이스팅 된다.
하지만 함수 표현식은 변수 선언과 할당을 구분할 수 있으므로 선언 부분만 호이스팅이 일어났다.
따라서 오류가 날 것으로 예상했던 코드가 예측한 대로 작동하질 않는다.
오류를 최소화하려는 전략이었는지 모르겠지만 의도대로 코드가 작동하지 않는 것은 옳지 않다.
소스 코드를 분석하다가도 본 적 없는 변수나 함수가 갑자기 튀어나오면 요 놈이 어디서 나온 녀석인지 혼란스럽게 하기 마련이다.
그래서 나는 차라리 오류를 안 나게 호이스팅을 하는 것보다 오히려 오류를 내는 쪽이 더 안전하게 코드를 설계하는 건 아닐까 싶다.
따라서 사용할 변수와 함수는 최소한 사용하기 전에 미리 선언해야 의도한대로 코드가 동작한다고 확신을 가질 수 있다.

ES2015+에서는?

이렇게 짧게 끝내기 아쉬우니 ES2015+에서는 호이스팅이 어떻게 처리되는지 알아보자.
일단 ES2015+에서는 변수를 선언할 때 const와 let으로 대신해서 쓴다.
이에 대해서는 다른 포스트에서 자세히 다룰테니 일단은 그런 놈들이 있다고만 알자.
그럼 똑같은 코드를 ES2015+ 식으로 작성해보자.

1
2
3
4
console.log(asdf); // Uncaught ReferenceError: asdf is not defined
console.log(qwer);
const asdf = 'asdf';
let qwer = 'qwer';

1번 라인에서 오류가 나서 2번 라인은 실행도 못하고 바로 뻗어버린다.
우리의 바람대로 호이스팅이 사라진 것 같다.
과연 정말일까…?

TDZ(Temporal Dead Zone)

임시적 사각 지대, 변수가 임시로 죽어있는 공간이라고 이해하면 될 것 같다.
뭔소린지 글로만 봐서는 모르니 코드를 통해서 보자.

1
2
3
4
5
6
const asdf = 'asdf';
const qwer = function() {
console.log(asdf); // Uncaught ReferenceError: asdf is not defined
let asdf = 'qwer';
}
qwer();

호이스팅이 일어나지 않았다면 3번 라인에서는 ‘asdf’가 찍혀야 정상일 것이다.
혹은 호이스팅이 일어났다면 undefined라도 찍혀야하는데 오히려 에러를 내뱉었다.
이건 무슨 경우일까? 답은 바로 TDZ!
위 코드를 다시 TDZ라는 것과 연관 지어서 풀어보겠다.

1
2
3
4
5
6
7
8
9
const asdf = 'asdf';
const qwer = function() {
let asdf; // 사실 이렇게 변수 선언부가 호이스팅에 의해 스코프 상단으로 끌어올려진다.
// 변수의 초기화 구문을 만나기 전까지 TDZ가 형성됨.
console.log(asdf); // TDZ에서 해당 변수가 쓰였다면 에러를 발생!
// 여기까지 TDZ.
asdf = 'qwer';
}
qwer();

TDZ는 변수 선언(호이스팅에 의해 스코프 상단으로 끌어올려진 부분)부터 변수의 할당을 만나기 전 부분까지 형성이 되는 구간인데
이 TDZ에서는 해당 변수가 임시적으로 죽어있는 구역인데 이 구역에 해당 변수가 끼어들면서 오류가 발생하게 되는 것이다.

마치며…

오늘 올린 호이스팅은 사실 스코프와 연관지어 생각해봐야 이해하기가 쉬운 부분이다.
따라서 다음에 포스팅 할 예정인 스코프 포스트까지 꼭 보길 바란다.