LHJ

I'm a FE developer.

7.8 함수 스코프와 호이스팅

05 May 2020 » js_lj

ES6에서 let을 도입하기 전에는 var를 써서 변수를 선언했고, 이렇게 선언된 변수들은 함수 스코프로 불리는 스코프를 가졌습니다(var로 선언한 전역 변수는 명시적인 함수 안에 있지는 않지만 함수 스코프와 똑같이 동작합니다).

let으로 변수를 선언하면, 그 변수는 선언하기 전에는 존재하지 않습니다.
var로 선언한 변수는 현재 스코프 안이라면 어디서는 사용할 수 있으며, 심지어 선언하기도 전에 사용할 수 있습니다.
예제를 보기 전에, 아직 선언되지 않은 변수와 값이 undefined인 변수는 다르다는 점을 상기해 봅시다.
아직 선언되지 않은 변수는 에러를 일으키지만, 존재하되 값이 undefined인 변수는 에러를 일으키지 않습니다.

let var1;
let var2 = undefined;
var1;           // undefined
var2;           // undefined
undefinedVar;   // ReferenceError: undefinedVar는 정의되지 않았습니다.

let을 쓰면, 변수를 선언하기 전 사용하려 할 때 에러가 일어납니다.

x;          // ReferenceError: x는 정의되지 않았습니다.
let x = 3;  // 에러가 일어나서 실행이 멈췄으므로 여기에는 결코 도달할 수 없습니다.

반면 var로 변수를 선언하면 선언하기 전에도 사용할 수 있습니다.

x;          // undefined
var x = 3;
x;          // 3

대관절 어떻게 된 걸까요?
변수를 선언하지도 않았는데 그 변수에 접근할 수 있다는 건 이해할 수 없는 일입니다.
var로 선언한 변수는 끌어올린다는 뜻의 호이스팅(hoisting) 이라는 매커니즘을 따릅니다.
자바스크립트는 함수나 전역 스코프 전체를 살펴보고 var로 선언한 변수를 맨 위로 끌어올립니다.
여기서 중요한 것은 선언만 끌어올려진다는 것이며, 할당은 끌어올려지지 않는다는 겁니다.
자바스크립트는 이전 예제를 다음과 같이 해석합니다.

var x;      // 선언(할당은 아닌)이 끌어올려집니다.
x;          // undefined
x = 3;
x;          // 3

좀 더 복잡한 예제를 살펴봅시다.
이번에는 원래 코드와 자바스크립트가 해석한 코드를 1:1로 비교하겠습니다.

// 원래 코드
if (x !== 3) {
	console.log(y);
	var y = 5;
	if (y === 5) {
		var x = 3;
	}
	console.log(y);
}
if (x === 3) {
	console.log(y);
}
// 자바스크립트가 해석한 코드
var x;
var y;
if (x !== 3) {
	console.log(y);
	y = 5;
	if (y === 5) {
		x = 3;
	}
	console.log(y);
}
if (x === 3) {
	console.log(y);
}

원래 코드가 모범적인 자바스크립트 코드라는 것은 아닙니다.
변수를 선언하기도 전에 사용하면 불필요하게 혼란스럽고, 에러에도 취약합니다.
이렇게 해야 할 현실적인 이유가 있는 것도 아닙니다.
이 코드를 제시한 이유는 호이스팅이 어떻게 동작하는지 잘 보여주기 때문입니다.

var를 이용해 변수를 선언하면 자바스크립트는 같은 변수를 여러 번 정의하더라도 무시합니다.

// 원래 코드
var x = 3;
if (x === 3) {
	var x = 2;
	console.log(x);
}
console.log(x);
// 자바스크립트가 해석한 코드
var x;
x = 3;
if (x === 3) {
	x = 2;
	console.log(x);
}
console.log(x);

이 예제를 보면 같은 함수나 전역 스코프 안에서는 var로 새 변수를 만들 수 없으며, let으로 가능했던 변수 숨김도 불가능함을 알 수 있습니다.
이 예제는 블록 안에서 두 번째 var문을 썼지만 변수 x는 하나뿐입니다.

다시 말하자면, 이런 스타일을 권하는 건 절대 아닙니다.
이런 스타일은 혼란을 초래할 뿐입니다.
대부분의 독자, 특히 다른 언어에 익숙한 독자들은 이 예제를 힐끗 보고 필자가 if 문의 블록 스코프 안에 새 변수 x를 만들려 한다고 생각할 수 있지만, 그런 일은 일어나지 않습니다.

var를 쓰면 혼란스럽고 쓸모없는 코드가 생길 수 있는데 왜 이런 키워드를 만들었는지 의문이 든다면 let 키워드가 만들어진 이유를 이해한 겁니다.
물론 var를 써도 명확하고 이해하기 쉬운 코드를 작성할 수 있지만, 혼란스럽고 불명확한 코드를 작성할 위험은 여전합니다.
var를 뜯어고치면 기존 코드가 모두 망가지므로 대신 ES6에서 let을 새로 만든 겁니다.

필자는 let 대신 var를 써서 더 좋은 코드나 더 명확한 코드를 쓰는 방법을 도저히 떠올릴 수 없습니다.
달리 말해 var에는 let보다 나은 점이 전혀 없습니다.
필자를 포함해, 자바스크립트 커뮤니티에서는 대부분 let이 언젠가 var를 완전히 대체할 것으로 예상합니다.
언젠가는 var가 최종적으로 폐기될 날이 올 수도 있습니다.

그럼 왜 var와 호이스팅을 이해해야 할까요?
이유는 두 가지 입니다.
ES6를 어디에서는 쓸 수 있으려면 아직 시간이 더 필요하므로 ES5로 트랜스컴파일을 해야 합니다.
기존 코드는 전부 ES5로 작성됐다는 건 말할 필요도 없겠죠.
따라서 아직은 var가 어떻게 동작하는지 이해하고 있어야 합니다.
두 번째 이유는, 함수 선언 역시 끌어올려진다는 겁니다.
이제 함수 호이스팅에 대해 알아봅시다.