var
는 Function-Level Scope
를 가지고, let
과 const
는 Block-Level Scope
를 가진다.
Function Scope
부터 하나씩 살펴보자.
function scope 란 함수에서 선언된 변수는 내부에서만 유효하고, 외부에서는 참조할 수 없다는 의미이다.
function test() {
var a = 1;
if(true) {
var b = 2;
if(true) {
var c = 3;
}
}
console.log(a, b, c);
}
test();
console.log(a, b, c);
위와 같이, test() 함수를 호출한 결과는 무엇일까? 정답은 1 2 3
과 마지막 console.log() 함수는 a, b, c 변수를 참조하지 못한다.
그 이유는 a
, b
, c
가 test()
함수 내부에서 유효한 동일한 scope를 가지고 있기 때문이다. 그리고 함수 밖인 전역 스코프에서는 a, b, c 변수가 없으므로 접근할 수 없는 것이다.
여기서 한가지 더 짚고 넘어갈게 있다. 렉시컬 스코프이다.
렉시컬 스코프는 함수의 실행 환경
이 아닌 함수를 정의한 환경
으로 참조하는 특징을 가지고 있다. 말이 어려우므로 예제를 통해서 살펴보자.
var a = 0;
function first() {
var a = 1;
second();
}
function second() {
console.log(a);
}
first();
예상을 해보면, 우선 second() 함수가 호출될 시점에 상위 스코프인 first() 함수 스코프를 참조해 '1'을 출력할 것이라고 생각할 수 있다. 하지만, 자바스크립트는 렉시컬 스코프의 정의에 따라 함수 호출 시점이 아닌 함수를 선언한 위치에 따라 상위 스코프가 결정된다. 따라서 second() 함수는 전역 스코프에서 선언이 되었고 상위 스코프는 전역 스코프가 되어 '0'이 출력된다.
그럼 아래 코드는 어떻게 출력이 될 지 짐작이 될 것이다.
var a = 0;
function first() {
var a = 1;
var second = function() {
console.log(a);
}
second();
}
first();
second 함수는 first() 함수 내부에 선언되어 상위 스코프는 first() 함수 스코프이고 '1'을 출력할 것이다.
뜬금없지만, 만약 var 를 선언하지 않는다면 어떻게 될까?
해당 변수는 전역 스코프에 선언이 된다.
function first() {
var second = function () {
a = 1;
}
second();
}
first();
console.log(a);
var
는 Function Scope로 많은 유연함을 제공했지만, 그로 인한 복잡성도 공존하고 있었다.
특히, 아래와 같은 재선언 가능
, Function Scope에서의 호이스팅
에 대한 복잡함이 있었다.
var a = 1;
var a = 2; // 에러 안남;;
b = 3;
var b; // hoisting 으로 인해 에러 발생하지 않음;;
따라서 ES6 이후 Block-Level Scope
라는 개념을 가진 let
, const
이 추가되었다. 물론 let
, const
도 생성
, 실행
단계를 거치기 때문에 Hoisting이 발생하지만, Block Scope
안에서 호이스팅이 발생하기 때문에 보다 엄격하게 변수를 선언할 수 있게 되었다.
또한, var
과 다르게 let
과 const
는 재선언시, Reference Error
가 발생한다.
Block Scope
가지는 let
, const
는 {}
기준으로 Function Scope
와 다르게 if, for, while, try/catch 문과 같은 문법에서 유용하게 사용될 수 있다.
let
은 변수에 재할당을 할 수 있다.
하지만, var
와 같이 선언 이전에 참조를 하게 되면 Reference Error가 발생한다.
console.log(foo) // Reference Error
let foo = 1;
console.log(foo); // 1
foo = 2;
console.log(foo); // 2
for(let item of [1, 2, 3]) {
console.log(item); // 1, 2, 3
}
위 예제와 같이, 이미 선언한 변수에 값을 재할당할 수도 있고, 반복문은 Block 스코프를 가지게 된다.
const
도 선언 이전에 참조를 하게 되면 Reference Error가 발생한다.
let
과 차이점은 변수에 재할당을 할 수 없고, 선언과 동시에 할당해야만 한다. 즉, 불변성을 보장한다.
console.log(foo) // Reference Error
const foo = 1;
console.log(foo); // 1
foo = 2; // Assignment to constant variable.
const 변수에 재할당하게 되면 에러가 발생한다.