ES6의 변수 선언과 블록 레벨 스코프
요즘 핫한 JS 관련 문법은 Typescript이다. 타입 선언이 가능할 뿐만 아니라 js의 모든 문법을 사용할 수도 있고, 제네릭 등의 기타 편리한 요소가 추가되었다. 이를 익히기 위해선 기본적으로 ES6에 대한 지식이 필요하다. 그래서 오늘부터 며칠간 ES6의 기본 문법에 대해 알아보고자 한다.
let, const
es5의 변수 선언은 var 키워드를 통해 했었다. 하지만 var 키워드의 문제점이 있었다. 우선 함수 레벨 스코프였기 떄문에 전역 변수를 남발할 수 있었고 변수의 참조 범위에 대한 문제도 있었다. 또한 var 키워드의 생략을 허용하였기 때문에 의도치 않게 변수를 전역화 하였다. 그 외에도 중복 선언이 허용되어 의도치 않게 변수의 할당값을 변경한다던지, 변수 호이스팅으로 인해서 변수를 선언하기 전에 참조가 가능했었다.
그래서 이러한 단점들을 보안하기 위해 등장한것이 let과 const이다.
- let
var 키워드에서 변수는 함수 레벨 스코프를 가졌다. 하지만 es6에서는 let을 지원한다. 우선적으로 이들은 블록레벨 스코프를 갖는다.
let foo = 123;
{
let foo = 456;
let bar = 456;
}
console.log(foo); // 123
console.log(bar); // ReferenceError: bar is not defined
변수 bar는 블록 내에서만 참조할 수 있기 떄문에 에러가 발생한다. 또한 중복선언이 금지되어 있어 중복선언 시 문법 에러가 발생한다.
- 호이스팅
물론 let과 const 역시 호이스팅이 발생한다. 자바스크립트에서는 모든 선언을 호이스팅하기 떄문이다. var 키워드는 선언과 초기화가 한 번에 이루어진다. 그래서 스코프에 변수를 등록하고 메모리에 공간을 확보한 후 undefined 값으로 초기화한다. 그렇게 하면 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않는다.
하지만 let의 경우는 조금 다르다. let의 경우는 선언과 초기화가 분리되서 진행된다. 즉 초기화가 변수 선언문에 도달했을 때 실행되기 때문에 참조에러가 발생하게 된다. 변수를 위한 메모리 공간이 확보되지 않았다는 의미이다.
이것을 일시적 사각지대라고 부른다.
let foo = 1;
{
console.log(foo); // ReferenceError
let foo = 2;
}
블록 안에서 foo를 호출하면 foo가 출력될 것 같지만, 참조 에러가 발생한다. ES6의 변수 선언을 블록레벨 스코프를 가지기 때문에 블록 안에서의 변수 foo는 호출당시 일시적 사각지대에 빠져있다.
- 클로저
아래 코드를 살펴보자.
var func = [];
for (var i = 0; i < 3; i++) {
func.push(function () {
console.log(i);
});
}
for (var j = 0; j < 3; j++) {
func[j](); // 3 3 3
}
여기서 for 루프의 변수 i는 전역변수이다. 따라서 반복문에서 계속 값이 할당되고 결국 마지막 값이 남아 3번 출력되는 것이다.
0,1,2를 출력하기 위해선 클로저를 사용하여야 한다.
var func = [];
for (var i = 0; i < 3; i++) {
(function (index) {
func.push(function () { console.log(index); })
}(i));
}
for (var j = 0; j < 3; j++) {
func[j](); // 0 1 2
}
여기서 인자 index가 자유변수가 된다. 즉시실행함수가 죽어도 해당 환경이 남아있기 때문에 결과값을 기억한다. 하지만 let을 사용하면
var func = [];
for(let i = 0; i < 3; i++) {
func.push(function () { console.log(i); });
}
for(let j = 0; j < 3; j++) {
console.dir(func[j]);
func[j]();
}
// // [Function]
// 0
// [Function]
// 1
// [Function]
// 2
여기서의 변수 i는 지역변수가 된다. 또한 자유변수로서 for 루프가 끝나도 변수 i를 참조하는 함수가 존재한다면 계속 유지된다.
- let은 전역에서 접근할 수 없다.
보통 전역 변수는 window.변수명으로 접근이 가능하지만, let은 전역 변수여도 보이지 않는 블록이 존재하기 때문에 window 키워드로 접근할 경우 undefined가 뜬다.
- const
const도 let과 마찬가지로 변수를 선언하는 키워드이지만, const는 상수(변하지 않는 값)을 위해서 사용한다. (반드시 그런 것은 아니다.)
let과 대부분 비슷하지만 그 차이점도 있다.
- 선언과 초기화
let은 재할당 해도 상관없지만 const는 재할당할 수 없다. 또한 const는 선언과 동시에 할당이 이루어져야 한다. 그냥 변수만 선언할 경우에 문법에러가 발생하게 된다.
- 상수
아래 예제를 살펴보자. (그냥 보기만 하셔도 됩니다.)
if (rows > 10) {} // ???
const MAXROWS = 10;
if (rows > MAXROWS) {}
첫 번째 문장에서는 사실 그냥 딱 보았을 때 무슨 의미인지 알기 힘들지만, const로 변수 이름을 잘 설정한다면 의미가 명확해지기 떄문에 가독성과 유지보수가 편해진다.
- 객체
위에서도 말했지만, const는 재할당이 금지된다. 즉 const의 타입이 객체인 경우에는 객체에 대한 참조를 변경할 수 없다.
하지만 객체의 프로퍼티는 보호되지 않기 때문에 재할당은 안되지만, 할당된 내용은 변경할 수 있다.
const user = { name: 'Lee' }
user.name = 'Jang';
console.log(user) // { name: 'Jang' }
이 의미는 할당된 주소값이 변경되지 않고 그 값 자체가 변화한다는 의미이다. 이렇기 떄문에 객체 타입에는 const가 매우 좋다.
- 요약
재할당이 필요하다면 let을 사용하자!
ES6와 Typescript를 위해서 var 사용을 자제하자!
재할당이 필요없는 타입에는 const를 사용하자!