var, let, const의 차이 - 변수 선언 및 할당

이게시글에 나오는 정보의 출처는
https://poiemaweb.com/es6-block-scope 입니다.

일단 ES6에 let와 const가 나오게된 배경에 대해 말하자면
var로 선언된 변수는 아래와 같은 특징이 있고, 이 부분에 대해 신경쓰지 않는다면 심각한 문제를 발생시킵니다.

  1. 함수 레벨 스코프(Function-level scope)
    • 함수의 코드 블록만을 스코프로 인정한다. 따라서 전역 함수 외부에서 생성한 변수는 모두 전역 변수이다.
      이는 전역 변수를 남발할 가능성을 높인다.
    • for 문의 변수 선언문에서 선언한 변수를 for 문의 코드 블록 외부에서 참조할 수 있다.
  1. var 키워드 생략 허용
    • 암묵적 전역 변수를 양산할 가능성이 크다.
  1. 변수 중복 선언 허용
    • 의도하지 않은 변수값의 변경이 일어날 가능성이 크다.
  1. 변수 호이스팅
    • 변수를 선언하기 이전에 참조할 수 있다.

여기서 대부분의 문제는 전역 변수로 인해 발생합니다.
간단한 어플리케이션의 경우, 전역변수는 사용하는데 편하다는 장점이 있지만, 불가피한 상황을 제외하고는 사용을 자제해야 합니다.
왜냐면, 전역 변수는 유효범위(scope)가 넓어서 어플리케이션의 크기가 크면 커질수록 어떻게 사용 될지 파악하기 힘들며,
비순수 함수에 의해 의도치않게 변경될 가능성도 있어 복잡성을 증가시키는 원인이 됩니다.
그렇기에 변수의 유효범위는 좁을수록 좋습니다.

ES6에서는 var의 이러한 특성들 때문에 let과 const 키워드를 도입했습니다.

스코프란?

  1. 블록 레벨 스코프
    대부분의 프로그래밍 언어는 블록 레벨 스코프(Block-level scope)를 따르지만
    자바스크립트는 함수 레벨 스코프(Function-level scope)를 따른다.

함수 레벨 스코프(Function-level scope)

함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.

블록 레벨 스코프(Block-level scope)
모든 코드 블록(함수, if 문, for 문, while 문, try/catch 문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하며
코드 블록 외부에서는 참조할 수 없다. 즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.

아래 예제를 살펴보자.

var foo = 123; // 전역 변수

console.log(foo); // 123

{
  var foo = 456; // 전역 변수
}

console.log(foo); // 456

여기서 나는 단순히 let과 const가 호이스팅을 막기위해 인줄 알았는데,

let과 const도 호이스팅이 일어난다.

그럼에도 let과 const를 사용하는 이유는 일시적 사각지대(Temporal Dead Zone; TDZ)에 있었다.

호이스팅이란?

자바스크립트는 ES6에서 도입된 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅한다.
호이스팅(Hoisting)이란, var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성을 말한다.

먼저 변수가 어떻게 생성되는지 부터 알아보자. 더 자세한건 Execution Context을 알아보자!

1. 선언 단계(Declaration phase)

- 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다. 이 변수 객체는 스코프가 참조하는 대상이 된다.

2. 초기화 단계(Initialization phase)

- 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다. 이 단계에서 변수는 undefined로 초기화된다.

3. 할당 단계(Assignment phase)

- undefined로 초기화된 변수에 실제 값을 할당한다.

var와 let 예시

// 스코프의 선두에서 선언 단계와 초기화 단계가 실행된다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 있다.
console.log(foo); // undefined

var foo;
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1

var와 let의 차이

  1. var은 함수 레벨 스코프(Function-level scope) let은 블록 레벨 스코프(Block-level scope)
  2. 변수 중복 선언 금지
    • var 키워드로는 동일한 이름을 갖는 변수를 중복해서 선언할 수 있었다. 하지만,
    • let 키워드로는 동일한 이름을 갖는 변수를 중복해서 선언할 수 없다. 변수를 중복 선언하면 문법 에러(SyntaxError)가 발생한다.

예시

var foo = 123;
var foo = 456;  // 중복 선언 허용

let bar = 123;
let bar = 456;  // Uncaught SyntaxError: Identifier 'bar' has already been declared
bar = 123은 가능하지만 중복 선언은 X

var의 동작

var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다.
즉, 스코프에 변수를 등록(선언 단계)하고 메모리에 변수를 위한 공간을 확보한 후, undefined로 초기화(초기화 단계)한다.
따라서 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않는다.
다만 undefined를 반환한다. 이후 변수 할당문에 도달하면 비로소 값이 할당된다.
이러한 현상을 변수 호이스팅(Variable Hoisting)이라 한다.

var는 아래와 같이 동작한다

let의 동작

let 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다.
즉, 스코프에 변수를 등록(선언단계)하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어진다.
초기화 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생한다. 이는 변수가 아직 초기화되지 않았기 때문이다.
다시 말하면 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문이다.
따라서 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없다.
스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 ‘일시적 사각지대(Temporal Dead Zone; TDZ)’라고 부른다.

이런 이유 덕에 var대신에 let을 사용해도 괜찮은 것이다.
마지막으로 const를 설명을 하자면, let과 공통점이 많기때문에, 차이점으로 설명을 하겠다.

선언과 초기화

let은 재할당이 자유로우나 const는 재할당이 금지된다.

const FOO = 123;
FOO = 456; // TypeError: Assignment to constant variable.

주의할 점은 const는 반드시 선언과 동시에 할당이 이루어져야 한다는 것이다.
그렇지 않으면 다음처럼 문법 에러(SyntaxError)가 발생한다.

const FOO; // SyntaxError: Missing initializer in const declaration

또한, const는 let과 마찬가지로 블록 레벨 스코프를 갖는다.

{
  const FOO = 10;
  console.log(FOO); //10
}
console.log(FOO); // ReferenceError: FOO is not defined

const와 객체

const는 재할당이 금지된다. 이는 const 변수의 타입이 객체인 경우,
객체에 대한 참조를 변경하지 못한다는 것을 의미한다.
하지만 이때 객체의 프로퍼티는 보호되지 않는다. 다시 말하자면 재할당은 불가능하지만
할당된 객체의 내용(프로퍼티의 추가, 삭제, 프로퍼티 값의 변경)은 변경할 수 있다.

const user = { name: 'Lee' };

// const 변수는 재할당이 금지된다.
// user = {}; // TypeError: Assignment to constant variable.

// 객체의 내용은 변경할 수 있다.
user.name = 'Kim';

console.log(user); // { name: 'Kim' }

객체의 내용이 변경되더라도 객체 타입 변수에 할당된 주소값은 변경되지 않는다.
따라서 객체 타입 변수 선언에는 const를 사용하는 것이 좋다.
만약에 명시적으로 객체 타입 변수의 주소값을 변경(재할당)하여야 한다면 let을 사용한다.

마무리 var vs. let vs. const

변수 선언에는 기본적으로 const를 사용하고 let은 재할당이 필요한 경우에 한정해 사용하는 것이 좋다.
원시 값의 경우, 가급적 상수를 사용하는 편이 좋다. 그리고 객체를 재할당하는 경우는 생각보다 흔하지 않다.
const 키워드를 사용하면 의도치 않은 재할당을 방지해 주기 때문에 보다 안전하다.

var와 let, 그리고 const는 다음처럼 사용하는 것을 추천한다.

ES6를 사용한다면 var 키워드는 사용하지 않는다.
재할당이 필요한 경우에 한정해 let 키워드를 사용한다. 이때 변수의 스코프는 최대한 좁게 만든다.
변경이 발생하지 않는(재할당이 필요 없는 상수) 원시 값과 객체에는 const 키워드를 사용한다.
const 키워드는 재할당을 금지하므로 var, let 보다 안전하다.
변수를 선언하는 시점에는 재할당이 필요할지 잘 모르는 경우가 많다. 그리고 객체는 의외로 재할당을 하는 경우가 드물다.
따라서 변수를 선언할 때에는 일단 const 키워드를 사용하도록 하자.
반드시 재할당이 필요하다면(반드시 재할당이 필요한지 한번 생각해 볼 일이다.)
그때 const를 let 키워드로 변경해도 결코 늦지 않는다.

글 정리 너무 어렵다...ㅠㅠ

좋은 웹페이지 즐겨찾기