React

ES6+ 변수를 정의하는 새로운 방법 ( const, let )

steadily 2019. 12. 11. 16:06

ES6+ 변수를 정의하는 새로운 방법 ( const, let )

새로운 방법이 나온 이유는 기존 방법으로는 해결되지 않는 문제가 있었기 떄문이다.

ES6 이전 var가 가진 문제들을 먼저 알아보자.


#1 var가 가진 문제 (함수 스코프)


스코프(scope) = 변수가 사용될 수 있는 영역

스코프는 변수가 정의된 위치에 의해 결정 된다.

var로 정의한 변수는 함수 스코프이기 때문에 아래와 같이 함수를 벗어난 영역에서 사용하면 에러가 발생한다.



function example() {
    var i = 1;
}
console.log(i); // 참조 에러


var 변수를 함수가 아닌 프로그램의 가장 바깥에 정의하면 전역 변수가 되는데, 이는 프로그램 전체를 감싸는 하나의 함수가 있다고 생각하면 이해가 쉽다.

하나 특이한 점은 함수 안에서 var 키워드를 사용하지 않고 변수에 값을 할당하면 그 변수는 전역 변수가 된다.



function example1() {
    i = 1;
}
function example2() {
    console.log(i);
}
example1();
example2(); // 1이 출력 됨.


이런 상황에서 명시적 에러가 발생하도록 하려면 파일 상단에 'use strict'를 선언하면 된다.

var는 함수 스코프이기 때문에 for 반복문에서 정의된 변수가 반복문이 끝난 이후에도 계속 남는 문제점이 있다.



for ( var i = 0; i < 10; i++ ) {
    console.log(i);
}
console.log(i); // 10


for 문뿐만 아니라 while 문, switch 문, if 문 등 함수 내부에서 작성되는 모든 코드는 같은 문재를 안고 있다.

var 변수의 스코프를 제한하기 위해 즉시 실행 함수를 사용하기도 한다. 즉시 실행 함수는 함수를 정의하는 시점애 바로 실행되고 사라진다.

var 변수는 함수 스코프이므로 즉시 실행 함수로 묶으면 변수의 스코프를 제한할 수 있다. 그러나  즉시 실행 함수는 작성하기 번거롭고 가독성도 떨어진다.



(function () {
    // statements
})()

var 변수의 스코프 문제를 해결하려면 이렇게 상당한 노력이 필요하다.


#2 var의 두번째 문제: 호이스팅

var로 정의된 변수는 그 변수가 속한 스코프의 최상단으로 끌어올려진다. 이를 호이스팅(hoisting)이라고 부른다.

끌어올려진다는 말의 의미가 무엇인지 지금부터 살펴보자.


다음 코드에서는 정의되지 않은 변수를 사용해서 에러가 발생한다.


console.log(myVar); // 참조 에러



이제 console.log 밑에 변수를 정의해 보자.


console.log(myVar); // undefined
var myVar = 1;


변수를 정의하기 전에 사용했음에도 위 코드를 실행하면 에러가 발생하지 않는다.

특이한 점은 1일 아니라 undefined가 출력된다는 점이다.

이것은 해당 변수의 정의가 위쪽으로 끌어올려졌기 때문이다.


다음처럼 변경됐다고 생각하면 이해하기 쉽다.



var myVar = undefined;
console.log(myVar); // undefined
myVar = 1;


변수의 정의만 끌어올려지고 값은 원래 정의했던 위치에서 할당된다.

특이하게도 다음처럼 변수가 정의된 곳 위에서 값을 할당할 수도 있다.


console.log(myVar); // undefined
myVar = 2;
console.log(myVar); // 2
var myVar = 1;


버그처럼 보이는 위 코드는 에러 없이 사용될 수 있는 것은 단점이라고 할 수 있다.

호이스팅은 직관적이지 않으며, 보통의 프로그래밍 언어에서는 찾아보기 힘든 성질이다.


#3 var의 기타 문제들

var의 또 다른 문제를 살펴보자. var를 이용하면 한 번 정의된 변수를 재정의할 수 있다.



var myVar = 1;
var myVar = 2;


변수를 정의한다는 것은 이전에 없던 변수를 생성한다는 의미로 통용된다. 

따라서 앞의 코드가 에러 없이 사용될 수 있다는 것은 직관적이지 않으며 버그로 이어질 수 있다.


또 다른 문제는 var가 재할당 가능한 변수로밖에 만들 수 없다는 점이다. 

상수처럼 쓸 값도 무조건 재할당 가능한 변수로 만들어야 한다. 

이런 상황에서 재할당 불가능 한 벼수를 사용한다면 코드의 복잡도가 낮아지고 가독성은 높아진다.




var의 문제를 해결하는 const, let


#1 const, let은 블록 스코프다.

var = 함수 스코프

const, let = 블록(block) 스코프

함수 스코프의 단점 대부분이 블록 스코프에는 없다.

블록 스코프는 대부분의 언어에서 사용하므로 개발자에게 익숙한 개념이다.


블록 스코프에서는 블록을 벗어나면 변수를 사용할 수 없다.


if (true) {
    const i = 0;
}
console.log(i); // 참조 에러


if 문의 블록 안에서 정의된 변수는 if 문을 벗어나면 참조할 수 없다.

따라서 if 문에서 생성된 변수를 블록 바깥에서 사용하려고 하면 에러가 발생한다.

이러한 상황에서 에러가 발생하는 것이 직관적이며 이해하기도 쉽다.

var를 사용하는 경우에는 if 문 안에서 생성된 변수가 if 문을 벗어나도 계속 살아 있기 때문에,

함수 스코프를 벗어나기 전까지는 계속해서 신경 써서 관리해야 한다.


이번에는 블록 스코프에서 같은 이름의 변수를 정의하는 경우를 살펴보자.


let foo = 'bar1';
console.log(foo); // bar1
if (true) {
    let foo = 'bar2';
console.log(foo); // bar2
}
console.log(foo); // bar1

마지막 줄의 foo 변수는 같은 블록에서 정의된 첫 줄의 변수를 참조하므로 'bar1'을 출력한다.


#2 const, let에서의 호이스팅

const 또는 let으로 정의된 변수도 호이스팅이된다.

하지만, const 또는 let으로 변수를 정의하기 전에 그 변수를 사용하려고 하면 참조 에러가 발생한다.



console.log(foo); // 참조 에러
const foo = 1;


따라서 const 또는 let으로 정의한 변수는 호이스팅이 되지 않는다고 생각하기 쉽다.

하지만 const 또는 let으로 정의된 변수도 호이스팅이된다.

다만 변수가 정의된 위치와 호이스팅된 위치 사이에서 변수를 사용하려고 하면 에러가 발생한다. 

이 구간을 임시적 사각지대(temporal dead zone)라고 한다.


임시적 사각지대에서 변수를 사용하지 못한다면 호이스팅의 역할은 무엇인지 생각해 보자.

다음 코드에서는 같은 이름의 변수가 서로 다른 스코프에 정의되어 있다.



const foo = 1; // 2
{
   console.log(foo); // 3 - 참조 에러
const foo = 2; // 1
}

만약 

1번 변수가 호이스팅되지 않았다면 => 3번 변수의 참조 에러는 발생하지 않고 2번 변수의 값이 출력될 것이다.

이것으로 호이스팅의 역할을 짐작할 수 있다.

1번 변수의 호이스팅 때문에 3번 변수는 1번 변수를 참조하게 된다.

그리고 1번 변수를 참조했지만 임시적 사각지대여서 에러가 발생한다.


이를 제대로 이해하기 위해서는 브라우저가 자바스크립트의 실행 환경을 어떻게 구축하는지 알아야 하는데

이번 주제의 범위를 벗어나므로 자세한 설명은 생략한다.


var로 정의된 변수에는 임시적 사각지대가 없기 때문에 다음 코드에서는 참조 에러가 발생하지 않는다.


var foo = 1;
(function() {
    console.log(foo); // undefined
var foo = 2;
})();



#3 const는 변수를 재할당 불가능하게 만든다.

const로 정의된 변수는 재할당이 불가능하다.

반대로 let, var로 정의된 변수는 재할당할 수 있다.

재할당 불가능한 변수는 프로그램의 복잡도를 상당히 낮춰주기 때문에 되도록이면 재할당 불가능한 변수를 사용하는게 좋다.


const bar = 'a';
bar = 'b'; // 에러 발생 1
var foo = 'a';
foo = 'b'; // 에러 없음
let value = 'a';
value = 'b'; // 에러 없음

1번 처럼 const로 정의된 변수에 값을 재할당하면 에러가 발생한다.


다만 const로 정의된 객체의 내부 속성값은 수정 가능하다는 점을 주의해야 한다.


const bar = { prop1: 'a'};
bar.prop1 = 'b';
bar.prop2 = 123;
console.log(bar); // { prop1: 'b', prop2: 123 }
const arr = [10, 20];
arr[0] = 100;
arr.push(300);
console.log(arr); // [100, 20, 300]

이미 존재하는 속성값을 수정하거나 새로운 속성값을 추가하는 것 모두 가능하다.

객체의 내부 속성값도 수정 불가능하게 만들고 싶다면 immer, immutable.js등의 외부 패키지를 활용하는 게 좋다.

이러한 외부 패키지는 객체를 수정하려고 할 때 기존 객체는 변경하지 않고 새로운 객체를 생성한다.


새로운 객체를 생성하는 기능은 필요 없고 단지 수정만 할 수 없도록 차단하고 싶다면, 다음과 같은 javascript 내장 함수를 이용하면 된다.


Object.preventExtensions

Object.seal

Object.freeze


당연한 이야기지만 const로 정의했다면 객체를 참조하는 변수 자체를 변경하는 것은 불가능하다.


const bar = { prop1: 'a'};
bar = { prop2: 123}; // 에러 발생