ES6(2015) 이전에는 자바스크립트에서 변수를 선언할 수 있는 방법은 var뿐이었습니다.

이것은 많은 문제를 발생시켰습니다.

  1. 전역 변수로 인한 스코프 충돌
    • ES6이전에 스코프는 전역 스코프(Global Scope)함수 스코프(Function Scope)만 있었습니다. 자바스크립트는 함수 스코프 외에는 모두 전역 스코프로 적용되기 때문에 전역 변수를 남발하여 스코프 충돌의 문제를 발생시켰습니다.
  2. 변수 중복 선언
    • 자바스크립트의 코드양이 많으면 많을 수록 스코프 충돌의 문제를 발생시킵니다. 이미 선언된 변수를 또 재선언을 하거나 다른 값을 넣거나하는 문제를 발생할 수 있습니다.

보통 전역변수로 인해 발생하는 문제로, 이를 해결하고자 ES6(2015)부터는 letconst Keyword를 도입했습니다.


let


ES6(2015)이전의 자바스크립트는 함수 내에서 선언된 변수 외 모든 코드 블록 내에서 선언된 변수는 전역 스코프(Global Scope)를 갖습니다.

Block Scope

ES6(2015) 이전

  var x = 10;

  {
      var x = 'change';
  }

  console.log(x);
  change

x에 10을 넣었지만, { } 블록 내부에 x를 재선언 되었기 때문에 change로 변경되어 출력되었습니다.


ES6(2015) let Keyword

  let x = 10;

  {
    let x = 'change';
  }

  console.log(x);
  10

{ } 코드 내부에 선언된 x블록 스코프를 따르기 때문에 외부에서 접근할 수 없습니다. 즉 재선언이 안된다는 말이죠. 따라서 전역 변수로 선언한 x=10이 출력되는 것을 확인할 수 있습니다.


  let x = 'Global'; // 전역 변수

  {
    let x = 'x Local'; // 지역 변수
    let y = 'y Local'; // 지역 변수
  }

  console.log(x); // Global
  console.log(y); // ReferenceError: bar is not defined
  Global
  Uncaught ReferenceError: y is not defined
    at <anonymous>:9:13

전역 스코프를 갖는 let x='Global'{ } 코드 내부에 선언한 let x = 'x Local'로 재선언되지 않으며 y블록 스코프를 따르기 때문에 외부에서 접근할 수 없기 때문에 console.logReferenceError 오류를 확인할 수 있습니다.


재선언 금지

var 키워드는 동일한 이름을 갖는 변수를 몇 번이고 재선언을 할 수 있습니다. 하지만 let 키워드는 동일한 이름으로 재선언을 할 수 없습니다. 중복 선언하게 되면 문법 에러(SyntaxError)를 발생시킵니다.

  var x = 10; // 최초 선언
  var x = 20; // 재 선언

  let y = 10; // 최초 선언
  let y = 20; // 재 선언

  console.log(x);
  console.log(y);
  Uncaught SyntaxError: Identifier 'y' has already been declared


호이스팅

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

예시를 먼저 보겠습니다.

  console.log(x);
  var x = 30;
  undefined

변수 x를 선언하기 전에 console.log로 출력을 했는데 x is not defined 오류가 발생하지 않았습니다. 왜 그런지 알아봅시다.

변수는 3단계를 걸쳐 생성됩니다.

1단계: 선언 단계(Declaration phase)

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

2단계: 초기화 단계(Initialization phase)

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

3단계: 할당 단계(Assignment phase)

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

var선언 단계초기화 단계가 한 번에 실행됩니다. 스코프에 변수를 등록(선언 단계)하여 메모리에 공간을 확보한 뒤, undefined초기화(초기화 단계)합니다. 이러한 이유로 변수선언문 이전에 변수에 접근하여도 에러가 발생하지 않고 undefined를 반환한 것입니다. 이후 변수에 값을 할당(할당 단계)해야 비로소 값이 할당 되는 것입니다. 이런 현상을 변수 호이스팅(Variable Hoisting)이라 합니다.

  // 스코프의 선두에서 선언 단계와 초기화 단게가 한 번에 이루어 집니다.
  // 따라서, 변수 선언 이전에 변수에 참조할 수 있습니다.
  console.log(x);  // undefined

  var x;           // 선언 단계
  console.log(x);  // undefined

  
  x = 10;          // 할당 단계
  console.log(x);  // 10
  undefined
  undefined
  10


let은 선언 단계와 초기화 단계가 분리되어 진행합니다.

let은 스코프에 변수를 등록(선언단계)은 하지만 초기화 단계는 변수 선언문에 도달했을 때 실행합니다. 따라서 초기화 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생합니다. 그 이유는 초기화 단게가 이루어지지 않았기 때문입니다. 즉, 변수를 담을 메모리 공간이 확보되지 않았기 때문입니다. 정리해서 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없습니다. 이 구간을 일시적 사각지대(Temporal Dead Zone; TDZ)라고 부릅니다.

  // 스코프의 선두에서 선언 단계가 이루어집니다.
  // 변수 초기화 단계는 이루어지지 않았습니다.
  // 따라서, 변수 선언 이전에 변수에 참조할 수 없습니다.
  console.log(x);  // Uncaught ReferenceError: x is not defined

  let x;           // 선언 단계
  console.log(x);  // undefined

  
  x = 10;          // 할당 단계
  console.log(x);  // 10
  Uncaught ReferenceError: x is not defined
    at <anonymous>:3:15


그럼 let은 호이스팅이 안되는게 아닐까?

  let x = 10;

  {
      console.log(x);
  }
  10

결과를 보면 선언단계초기화단계가 먼저 이루어졌기 때문에 정상적으로 결과값이 출력됩니다.


아래 코드에 let 선언을 추가해봅니다.

  let x = 10;

  {
      console.log(x);
      let x = 20;
  }
  Uncaught ReferenceError: Cannot access 'x' before initialization

전역변수 xconsole.log에 출력될 것 처럼 보일 수 있다. 하지만, { } 블록안에서 호이스팅이 발생하기 때문에 참조 에러(ReferenceError)가 발생하게 됩니다.

ES6letconst 키워드는 코드블록 내에서는 블록 스코프를 따르므로 { }블록 내에 선언 된 let x는 지역 변수로 적용됩니다. 따라서 { } 블록 안 스코프에서 호이스팅이 적용되고 코드 블록의 선두에서 초기화가 이루어 지는 지점까지는 일시적 사각지대(TDZ)에 빠지게 됩니다. 그렇기 때문에 참조 에러(ReferenceError)가 발생하게 됩니다.


let은 전역 객체가 아니다

전역 객체(Global Object)란 모든 객체의 최상위 객체를 의미합니다.

Browser-side에서는 window 객체, Server-side(Node.js)에서는 global 객체를 의미합니다.

여기서 var로 선언된 변수를 전역 변수로 사용하게 된다면 전역 객체의 프로퍼티가 됩니다.

하지만, let로 선언된 변수를 전역 변수로 사용하게 되더라도 전역 객체의 프로퍼티가 아닙니다.

다시말해, window 객체로 접근할 수 없으며, global 객체에만 접근할 수 있습니다.

  var x = 'Global Object'; // var 키워드 선언

  console.log(window.x);   // var 키워드는 전역 객체(Global Object)의 프로퍼티가 되므로 window 객체 접근 가능
  Global Object
  let x = 'Global Object'; // let 키워드 선언

  console.log(window.x);  // let 키워드는 전역 객체(Global Object)의 프로퍼티가 되므로 window 객체 접근 불가능
  undefined


const


const는 상수(변하지 않는 값)을 위해 사용하는 키워드입니다.

constlet과 대부분 비슷하지만 다른점에 대해서만 알아보겠습니다.


선언과 초기화

let 의 특징

  • 중복선언 불가능
    let x=20;  // 최초 선언
    let x=50;  // 중복 선언
    
    Uncaught SyntaxError: Identifier 'x' has already been declared
    


  • 재할당 가능
    let x=30;   // 최초 선언
    x =20;      // 재할당
    
    console.log(x); // 20 출력 
    
    20
    


const의 특징

  • 중복선언 불가능
    const x=20;  // 최초 선언
    const x=50;  // 중복 선언
    
    Uncaught SyntaxError: Identifier 'x' has already been declared
    


  • 재할당 불가능
    const x=30;   // 최초 선언
    x =20;      // 재할당시 문법 에러 발생
    
    Uncaught TypeError: Assignment to constant variable.
    


  • const의 키워드는 반드시 선언과 동시에 할당이 이루어져야 합니다. 그렇지 않으면 이미 선언된 변수에 값을 할당할 수 없습니다. 그 이유는 재할당이 안되기 때문입니다.
    const x;    // 초기화
    x =20;      // 할당
    
    Uncaught SyntaxError: Missing initializer in const declaration
    


const의 객체

  • const는 재할당이 되지 않는다고 말씀드렸습니다. 하지만, 객체의 프로퍼티는 변경할 수 있습니다.
    const obj = {'name' : 'seongsik'};
    
    obj.name = 'good';
    
    console.log(obj);
    
    {name: 'good'}
    

const 키워드는 객체의 내용이 변경(추가, 변경, 삭제)되더라도 객체 타입 변수에 할당된 메모리 주소값은 변경되지 않습니다. 따라서, 객체 타입 변수 선언에는 const를 사용하는 것이 좋습니다. 만약 객체 타입 변수의 주소값을 변경(재할당)해야 한다면 let을 사용합니다.


끝으로

  • ES6(2015)+를 사용한다면 var를 사용하지 않는 것이 좋습니다.
  • 자바스크립트 코드양이 많아질 수록 의도치 않은 재할당 실수를 하게되는데 이를 방지하기 위해 const를 사용합니다.
  • 재할당이 필요한 경우에는 let을 사용합니다.