과거에는 let, const가 존재하지 않았기 때문에 var을 주로 사용함.
호이스팅
자바스크립트에서 변수 또는 함수를 스코프 내 최상위로 이동한 것처럼 동작하는 특성. 용어 자체는 ECMAScript 사양에 등장한 적이 없지만, 실제로는 자신이 속한 스코프의 최상단으로 이동하는 특성을 가지고 있으므로 호이스팅이라는 표현을 사용하여 설명한다고 한다.
호이스팅에 의해 선언 전에 사용한 경우는 자바스크립트 내에서 2가지 경우만 해당한다.
- var
- 함수 표현식(function expression)
var apple = 'this is apple';
v();
console.log(apple);
e();
function v() {
apple = 'inner apple';
console.log(apple);
console.log(ball);
var apple;
var ball = 'bal';
console.log(ball);
}
const e = function() {
console.log("it makes error if used before declaration");
};
위 결과를 살펴보자.
- inner apple: var은 함수 스코프 또는 전역 스코프를 가지고 있다. 함수 v 내부에 선언된 var apple 부분이 함수 상단으로 호이스팅 되고, 값 할당을 거친 후 출력되어 "inner apple"이 출력된다.
- undefined: 호이스팅은 var 변수 자체만 위로 올리며, 값은 undefined가 된다. 할당 부분인 ball='bal'은 해당 라인이 지나간 후에 처리된다. 따라서 이 시점에 ball === undefined이므로 undefined가 출력된다.
- bal: bal='bal'에 의해 ball 변수에 'bal'이 할당된 이후이다. 따라서 'bal'이 출력된다.
- this is apple: 전역 스코프에 정의된 apple을 출력한다. v 함수 내부에서 출력하는 apple이 전역 스코프에 존재하는 변수가 아님을 보이기 위해 넣었다.
- e() 관련 에러: 같은 함수라도 함수 표현식(function expression)이 아니라면 호이스팅 될 때 함수 정의까지 최상위로 이동하지 않는다. e는 let 변수이므로, 호이스팅에 의해 최상위로 올라가더라도 초기화는 코드 상의 위치에서 처리된다.
마지막 에러 부분에 대해 하나만 더 해보자. 함수 표현식이라도 결국 var, let, const에 의해 정의된 변수이므로 해당 키워드들의 특성을 따라간다.
function test3() {
console.log('global scope test');
}
function test2() {
test3 = function() {
console.log('function scope test');
};
test3();
var test3;
}
test2();
test3();
test2 함수 내부에서 var 변수 test3은 호이스팅에 의해 최상위로 이동, undefined으로 초기화된다. 이후 함숫값을 할당하고 실행한 결과 "function scope test"를 출력했다. 이번에도 역시 test3 할당 부분이 전역 스코프의 test3을 덮어쓴 것이 아님을 보이기 위해 외부 test3을 출력했다. 둘은 확실히 다르다.
호이스팅에 대해서는 var 키워드인지, 함수 표현식인지만이 중요하다. 나머지 함수 선언문이든, 화살표 함수이든간에 어차피 해당 함수는 var, let, const 키워드로 선언된 변수로 취급된다.
var
자바스크립트 환경에서 유서 깊게 사용되어 온 키워드이다. 특징은 다음과 같다.
- var은 함수 스코프 또는 전역 스코프를 따른다.
- var은 중복 선언이 가능하다. 단, 2번째 선언부터는 그냥 값을 덮어 쓴다.
- var은 선언 전에 사용할 수 있다. (정확히는 호이스팅 시 undefined로 초기화)
var이 함수 스코프 & 전역 스코프를 따른다는 것은, 반대로 말하면 블럭 스코프에 대해 적용되지 않는다는 것이다.
{
var hello = "world"; // 함수 스코프, 전역 스코프만 존재
}
console.log(hello);
// 함수 스코프, 전역 스코프만 가짐
function makeArmy() {
const arr = [];
console.log("before act", i);
for(var i = 0; i < 10; i++) { // 블럭 스코프 없으므로, i는 전역 스코프에서 참조하게 된다
const func = function() {
console.log(i);
}
arr.push(func);
}
console.log("after act", i);
return arr;
}
const army = makeArmy();
army[0]();
army[5]();
첫번째 출력된 world의 경우, 블럭 내에서 선언되었지만 블럭 외부에서 문제 없이 사용할 수 있는 모습을 볼 수 있다.
숫자 예시는 https://ko.javascript.info/closure#ref-28 의 정답에서 let을 var 키워드로 변경하기만 했다. 원래 답에서는 각 i의 값이 블럭 단위 렉시컬 환경에 존재하기 때문에, func가 일종의 클로저로서 각기 다른 i를 참조하도록 구현할 수 있었다.
그런데, 이를 var로 변경하면 var 변수는 함수 스코프를 따르기 때문에 makeArmy의 최상위로 호이스팅된다. 따라서 모든 func 함수는 동일한 var 변수 i를 참조하게 되고, 모두 같은 값을 출력하게 된다.
console.log(a);
var a = 10;
console.log(a);
var a = 20;
console.log(a);
var a = {hello: 'world'}
console.log(a);
var은 중복 선언이 된다. 첫번째 var 선언 이후에는 할당만 된다. 맨 위 undefined는 호이스팅에 의해 a 값이 최상위로 이동하면서 undefined로 초기화되었기 때문이다. 이를 통해 var 변수의 경우 선언 전에 사용할 수 있음을 알 수 있다.
let, const
var 키워드에는 블럭 레벨 스코프가 없다는 점, 중복 선언으로 인해 의도하지 않은 덮어쓰기가 가능하다는 점, 상수를 정의할 수 없다는 점 등 불편한 점이 좀 있다.
let, const 키워드는 이러한 var 키워드의 불편함을 해소하기 위해 ES6 시절에 등장한 키워드로, 나는 사실 자바스크립트를 ES6 이후부터 접했기 때문에 var 변수보다는 이 키워드들이 더 익숙하다. 특징은 다음과 같다.
- ES6 시절에 도입
- 블럭 레벨 스코프(block-level scope)를 가진다.
- 중복 선언할 수 없다.
- 호이스팅에 의해 스코프 최상위로 올라오긴 하지만, 선언 전까지 사용 불가능하다.
- let은 변수, const는 상수 선언에 사용할 수 있다.
사실 언급한 특징 모두 많은 언어에 공통된 부분이기 때문에 var 키워드를 몰랐다면 솔직히 그냥 넘어가도 될 수준이다. 그러나 우리는 이미 var 키워드를 알고 있기에, 달라진 부분에 대해 생각해보자.
블럭 스코프
let x = 1;
switch (x) {
case 0:
let foo;
break;
case 1:
let foo; // 재선언으로 인한 SyntaxError
break;
}
블럭 스코프는 다른 언어에서 당연한 부분이라 딱히 언급할 부분은 없다. switch 문을 사용하는 경우 블럭 스코프가 하나라 동일한 이름의 변수를 선언할 수 없다는 부분만 주의하자.
호이스팅
개인적으로 4번이 큰 변화라고 생각하면서도, 특이한 것 같다. 호이스팅의 영향을 받기는 하는데 선언 전까지 사용할 수는 없다는 점 때문이다. let, const 키워드에 의해 선언된 변수는 다음과 같이 처리된다고 한다.
- 호이스팅에 의해 최상위로 올라가되, 초기화는 진행되지 않는다. 이를 통해 컴파일러는 변수의 존재를 알지만, 변수를 사용할 수는 없는 상태가 된다.
- 변수가 선언된 위치에 도달하면 변수를 초기화한다.
- 변수가 할당되는 위치에 도달하면 변수를 할당한다.
1 과정은 확실히 존재한다. 이는 let 변수를 선언해두고 콘솔로 찍어보면 확인할 수 있다.
console.log(a);
let a = 10;
발생한 에러 메시지를 잘 보면, 변수 a가 초기화(initialization) 되기 전에 사용할 수 없다고 한다. 만약 a가 존재하지 않는다고 생각했다면 다음과 같은 에러를 발생시켰을 것이다.
위 결과는 a 선언을 제거하고 출력했을 때 나타나는 에러이다. 이 경우 a가 정의되지 않았다고 말한다. 즉, 변수에 대해 호이스팅은 분명히 존재한다. 스코프를 평가하는 시점에 모든 변수는 최상위로 호이스팅 된다.
let, const은 호이스팅은 되지만 초기화는 선언 부분에서 처리되는 요상한 성격을 가지고 있다. 따라서 호이스팅과 선언 시점 사이에 변수가 초기화되지 않아 접근할 수 없는 영역이 발생한다. 이 영역을 Temporal Dead Zone이라고 부른다. 애초에 값을 선언 전에 사용할 생각을 하지는 않겠지만, let / const에 대해 호이스팅이 초기화를 해주지 않음을 명심하자.
'javascript' 카테고리의 다른 글
[nestjs] nestjs swagger (0) | 2023.09.21 |
---|---|
[자바스크립트] 시간 차이 측정하기 (0) | 2023.01.21 |