ES6. let, const

Variable

자바스크립트에서 코드가 실행되기 바로 전에 컴파일러를 거치는데, 이 단계를 Lexing이라고 한다. 이 단계에서는 Scope가 정의되고 변수 및 함수 선언이 범위의 맨 위로 이동한다.

모든 선언이 먼저 처리되며 이는 변수 및 함수에도 똑같이 적용된다.

자바스크립트는 먼저 코드를 해석하고 변수가 존재하는지 확인하여 범위의 맨 위로 이동한다. 이를 호이스팅(hoisting)이라 한다.

let, const

Block-level Scope로 코드 블록 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조 할 수 없다.

// # Function-level scope
{
  var number = 123;
}
console.log(ES5); // 123

// # Block-level scope
{
  let number = 123;
}

console.log(ES6); // Uncaught ReferenceError: ES6 is not defined

let

  • 선언된 변수는 다시 선언할 수 없다.
  • 재할당이 필요한 변수에는 let을 사용한다.
let foo = 123;
let foo = 222; // Uncaught SyntaxError: Identifier 'foo' has already been declared

자바스크립트는 let, const를 포함하여 모든 선언을 호이스팅한다.

호이스팅: 선언문을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성

다만, var 키워드와 달리 ES6의 let, const 키워드로 선언된 변수들은 선언문 이전에 참조하면 참조 에러가 발생한다. 이유로는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문이다.

변수는 3단계에 걸쳐 생섭되는데,

  1. 선언 단계: 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록. 이 변수 객체는 스코프가 참조하는 대상이 된다.
  2. 초기화 단계: 변수 객체에 등록된 변수를 위한 공간을 메모리에 저장한다. 이 단계에서 변수는 undefined로 초기화 된다.
  3. 할당 단계: undefined로 초기화된 변수에 실제 값을 할당한다.

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

console.log(foo); // undefined

var foo;  // 선언과 초기화가 한번에
console.log(foo); // undefined

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

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

console.log(foo); // ReferenceError: foo is not defined

let foo;  // 선언 단계에서 초기화 단계가 실행
console.log(foo); // undefined

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

const

  • let은 재할당이 자유로우나 const는 재할당이 금지한다.
  • const는 대게 상수(변하지 않을 값)에 사용한다. 상수는 대문자로 표기한다.
const foo= 123;
foo = 123;  // TypeError: Assignment to constant variable.

const는 반드시 선언과 동시에 할당이 이루어져야 한다.

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

다만 const 변수의 타입이 객체인 경우, 객체에 대한 참조를 변경하지 못한다. 객체의 프로퍼티는 보호되지 않는다.

재할당은 불가능하지만 할당된 객체의 내용은 변경할 수 있다.

const user = { name: 'Kim' };
user.name = 'Lee';

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

원시 타입 변수 선언에는 const를 사용하는 것을 추천한다. 다만 원시 타입의 주소값을 변경(재할당)해야 한다면 let을 사용해야 한다.


Template Literals

사용법으로는 ‘(single quotes) 또는 “(double quote) 대신에 `(backtick) 문자를 사용한다.

const first = 'Jae-gyun';
const last = 'Kim';

// ES5: 문자열 연결
console.log('My name is ' + first + ' ' + last + '.');

// ES6: 문자열 연결
console.log(`My name is ${fisrt} ${last}.`);

파라미터, Rest 파라미터, Spread 연산자

파라미터 기본값

// ES5
function plus(x, y) {
  x = x || 0; // 매개변수 x에 인수를 할당하지 않은 경우, 기본값 0을 할당
  y = y || 0; // 매개변수 y에 인수를 할당하지 않은 경우, 기본값 0을 할당
  return x + y;
}

// ES6
function plus(x = 0, y = 0) {
  return x + y;
}

Spread 연산자

Spread 연산자는 ...를 사용하여 배열을 개별 요소로 분리한다.

console.log(...[1, 2, 3]) // 1, 2, 3
  • 함수의 인자로 사용하는 경우
// ES5
function foo(x, y, z) {
  console.log(x); // 1
  console.log(y); // 2
  console.log(z); // 3
}

// 배열을 foo 함수의 인자로 전달
const arr = [1, 2, 3];

// apply 함수의 2번째 인자는 호출하려는 함수(foo) 개별 인자로 전달
foo.apply(null, arr);
// ES6
function foo(x, y, z) {
  console.log(x); // 1
  console.log(y); // 2
  console.log(z); // 3

  const arr = [1, 2, 3];

  /* ...[1, 2, 3]는 [1, 2, 3]을 개별 요소로 분리한다. (1, 2, 3)
  spread 연산자에 의해 분리된 배열의 요소는 개별적인 인자로서 각각의 매개변수에 전달된다. */
  foo(...arr);
}

Rest 파라미터

Rest 파라미터는 Spread 연산자(...)를 사용하여 파라미터를 정의한 것을 의미한다. Rest파라미터를 사용하면 인수의 리스트를 함수 내부에서 배열로 전달받을 수 있다.

function foo(...res) {
  console.log(Array.isArray(rest))  // true
  console.log(rest);  // [1, 2, 3, 4, 5]
}

foo(1, 2, 3, 4, 5)

객체

프로퍼티 축약

// ES5
vax x = 1, y = 2;

var obj = {
  x: x,
  y: y,
};

console.log(obj); // { x: 1, y: 2 }

ES6에서는 객체 리터럴의 프로퍼티, key와 value가 같다면 하나의 keyword로 축약가능 하다.

// ES6
let x = 1, y = 2;

const obj = { x, y };
console.log(obj); // { x: 1, y: 2 }

__proto__ 프로퍼티에 의한 상속

ES5에서 객체 리터럴을 상속하기 위해서는 Object.create() 함수를 사용한다.

// ES5
var parent = {
  name: 'parent',
  sayHi: fnction() {
    console.log('Hi! ' + this.name);
  }
};

// 프로토타입 상속
var child = Object.create(parent);
child.name = 'child';

parent.sayHi(); // Hi! parent
child.sayHi();  // Hi! child

ES6에서는 객체 리터럴 내부에서 __proto__ 프로퍼티를 직접 설정할 수 있다.

// ES6
const parent = {
  name: 'parent',
  sayHi() {
    console.log(`Hi! ${this.name}`);
  }
};

const child = {
  // child 객체의 프로토타입 객체에 parent객체를 바인딩하여 상속을 구현
  __proto__: parent,
  name: 'child'
};

parent.sayHi(); // Hi! parent
child.sayHi();  // Hi! child

객체 디스트럭처링

// ES5
var obj = { firstName: 'JaeGyun', lastName: 'Kim' };

var firstName = obj.firstName;
var lastName = obj.lastName;

console.log(firstName, lastName); // JaeGyun Kim

ES6의 객체 디스트럭처링은 객체의 각 프로퍼티를 객체로부터 추출하여 변수 리스트에 할당한다.

// ES6
var obj = { firstName: 'JaeGyun', lastName: 'Kim' };

const { firstName, lastName } = obj;

/*
  const firstName = obj.firstName; => const { firstName } = obj;
  const lastName = obj.lastName; => const { lastName } = obj;
*/

console.log(firstName, lastName); // JaeGyun Kim

배열

배열 디스트럭처링

// ES6 Destructuring
const arr = [1, 2, 3];

// 배열의 인덱스를 기준으로 배열로 부터 요소를 추출하여 변수에 할당
const [one, two three] = arr;
console.log(one, two, three);   // 1 2 3

Class

자바스크립트는 프로토타입 기반 객체지향 언어이다.

프로토타입 기반 프로그래밍은 클라스가 필요없는 객체지향 프로그래밍 스타일로 프로토타입 체인과 클로저등으로 객체지향 언어의 상속, 캡술화(정보 은닉) 등의 개념을 구현할 수 있다.

// ES5
var Person = (function() {
  // Constructor
  function Person(name) {
    this._name = name;
  }

  // public method
  Person.prototype.sayHi = function() {
    console.log('Hi! ' + this._name);
  };

  // return constructor
  return Person;
})();

var person = new Person('Kim');
person.sayHi(); // Hi! Kim.

console.log(person instanceof Person);  // true

아래는 위의 Person 생성자 함수를 클래스로 정의한 것이다.

class Person {
  constructor(name) {
    this._name = name;
  }

  sayHi() {
    console.log(`Hi! ${this._name}`);
  }
};

const me = new Person('Kim');
me.sayHi(); // Hi! Kim

console.log(me instanceof Person);  // true

Class Property

클래스 내부에는 메소드만 선언할 수 있다. 클래스 내부에 변수를 선언하면 문법 에러가 발생한다.

class Foo {
  name = '';  // SyntaxError
}

클래스 프로퍼티의 선언과 초기화는 constructor 내부에서 일어난다.

class Foo {
  constructor(name = '') {
    this.name = name;
  }
}

const foo = new Foo('Kim');
console.log(foo); // Foo { name: 'Kim' }

Polyfiils

Polyfills는 가능한 경우 최신 환경에서 이전 환경으로 동등한 동작을 정의하기위한 패턴


Written by@Jkun
...

GitHub