ECMAScript 2015

Nessun (토론 | 기여)님의 2019년 11월 13일 (수) 16:11 판 (→‎컬렉션)
ECMAScript 표준 개정판
ECMAScript 5
ECMAScript 2015
ECMAScript 2016
ECMAScript 2017
ECMAScript 2018
ECMAScript 2019
ES Next

스펙 문서

개요

ECMAScript 2015는 2015년 6월에 승인된 ECMAScript 표준(JavaScript)의 6번째 개정판이다. ES6이라고도 부른다. 1999년에 나온 ES3 이후 2009년 ES5의 작은 변경만 빼면 거의 16년간 변화가 없던 자바스크립트, 더 나아가 웹 생태계에 대격변을 가져온 업데이트이다. 이 개정판부터 버전을 연도로 지칭하게 되었으며, 매년 새 개정판이 나오게 되었다.

브라우저 지원

ES6 호환성 테이블

인터넷 익스플로러는 가장 최신 버전인 IE11도 ES6의 기능들을 거의 이용할 수가 없다. 모질라 파이어폭스구글 크롬, 마이크로소프트 엣지, 사파리 등, 최신 업데이트가 계속 나오는 브라우저들은 2019년 기준으로 ES6의 기능들을 거의 다 지원하고 있다. 삼성 브라우저나 모바일 크롬과 같은 모바일 브라우저 역시 대부분 ES6을 지원하고 있다. 그러나 그놈의 인터넷 익스플로러가 아직 살아 있어 이를 지원하기 위해 Babel과 같은 트랜스파일러가 많이 사용되고 있다. Babel은 ECMAScript 2015와 그 이후 개정판의 기능을 사용한 코드를 ES5 문법으로 변경해주어 IE11 등에서 작동하도록 해준다.

추가 및 변경점

let, const

ES6 이전에는 변수를 선언하기 위해 var키워드를 사용했다. 기존 var로 선언한 변수는 함수 스코프를 가지고 있었지만 letconst로 선언한 변수나 상수는 블록 스코프를 가진다. 즉 중괄호 블록을 벗어나면 변수를 참조할 수 없게 된다.

{
  var a = 42
}
console.log(a) // 42

{
  let b = 42
}
console.log(b) // ReferenceError: b is not defined

const로 선언하면 값을 변경할 수 없는 상수가 된다.

const x = 0;
x = 3; // 에러가 발생한다.

var로 선언한 변수와는 달리, let을 이용해 같은 스코프 내에 같은 이름의 변수를 두 번 만들면 에러를 발생시킨다. var로 선언한 변수를 let으로 다시 선언하거나, 그 반대의 경우에도 에러가 발생한다.

var a = 3;
var a = 7; // 문제 없음

let b = 3;
let b = 7; // SyntaxError: redeclaration of let b

let a = 3; // SyntaxError: redeclaration of var a
var b = 3; // SyntaxError: redeclaration of let b

let을 이용해 for 반복문 내에서 클로저를 더 쉽게 사용할 수 있게 되었다. 다음 예제를 보자.

var arr = [];
for (var i = 0; i < 3; i++) {
  arr.push(function() { console.log(i) });
}
arr[0](); // 3
arr[1](); // 3
arr[2](); // 3

i를 출력하는 함수를 세 개 만들어 배열에 넣었는데, 결과는 모두 3을 출력하고 있다. 반복문이 끝나고 나면 i의 값이 3이 되기 때문이다. 반면 다음과 같이 let을 사용하면 좀 더 사용자가 원하는 동작을 볼 수 있다.

var arr = [];
for (let i = 0; i < 3; i++) {
  arr.push(function() { console.log(i) });
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2

let을 이용해 변수를 선언시 매 반복마다 새 변수 i가 선언되기 때문에, 다음 반복이 일어나도 기존에 클로저에 캡쳐된 변수의 값이 변하지 않는다.

컬렉션

다른 객체들을 담기 위한 컬렉션 객체들이 추가되었다. Map, Set, WeakMap, WeakSet이 새로 추가된 객체들이다.

Map은 키-값 쌍(key-value pair)을 저장할 수 있는 컬렉션이다. 이미 자바스크립트의 객체({})는 키-값의 쌍을 저장하기는 하지만 객체의 기본 내장 메서드들과 이름이 충돌하는 문제가 있을 수 있고, 키가 문자열과 ES6에서 추가된 Symbol로만 한정되는 문제가 있다. Map은 데이터를 추가하거나 가져오는 메서드를 분리하여 데이터와 내장 메서드가 충돌하는 문제를 해결하였고, 문자열 뿐 아니라 숫자나 심지어 다른 객체를 키로 사용할 수 있다. Map은 다음과 같이 사용한다.

let map = new Map();
map.set(300, "aa");
let keyObject = { a: "b" };
map.set(keyObject, "bb");

console.log(map.get(300)); // "aa"
console.log(map.get(keyObject)); // "bb"
map.set(keyObject, "cc");
console.log(map.get(keyObject)); // "cc", 같은 키를 두 번 사용하면 값을 덮어쓴다.

Set집합 자료형을 구현한 것으로, 값들을 저장하되 중복 없이 딱 한 번씩만 저장한다. 값이 존재하는지 여부를 빠르게 검사할 수 있게 된다.

let set = new Set();
set.add("aaaa");
console.log(set.has("aaaa")); // true
console.log(set.has("bbbb")); // false
set.add("aaaa"); // 이미 "aaaa"가 존재하므로 무시됨

MapSet 모두 데이터를 넣은 순서를 그대로 유지하기 때문에, 다음과 같이 순회할 때 일정한 순서를 보장한다. Python의 MapSet에는 이러한 보장이 없다.

let set = new Set();
set.add("aaaa");
set.add("bbbb");
set.add("cccc");
for (let i of set) {
  console.log(i); // "aaaa", "bbbb", "cccc" 순서대로 출력
}

V8의 구현에 따르면 MapSet 모두 해시를 이용해 시간복잡도 [math]\displaystyle{ O(1) }[/math]로 빠르게 데이터를 넣고 존재여부를 판단하거나 값을 가져올 수 있다.

Map, Set의 경우 키와 값에 대해 강한 레퍼런스를 가지고 있기 때문에, 키(Set의 경우에는 키=값으로 생각하면 된다)를 정의한 블록에서 벗어나 더는 그 키를 이용할 수 없게 되었더라도 Map이나 Set이 살아있는 한 키와 값이 계속 메모리를 차지하고 있게 된다. 즉, 메모리 누수의 위험이 항상 있다.

이러한 문제를 해결하기 위해 도입된 것이 WeakMapWeakSet이다. 이들은 각각 Map, Set과 같은 기능을 수행한다. 단 여기 사용된 키와 값은 가비지 컬렉션을 막지 않고, 가비지 컬렉션이 일어나면 사라지게 된다. 원래 객체의 수명 동안에만 필요한 추가 데이터를 저장한다든지 할 때 유용하게 사용할 수 있다. 단 WeakMapWeakSet은 넣은 객체들을 순회하거나, 가진 키-값 쌍을 모두 배열로 바꾸는 기능 등은 지원하지 않는다. 이 기능이 없기 때문에 키에 대한 레퍼런스를 잃어버리면 컬렉션에서도 접근할 수 없게 되어 안전하게 가비지 컬렉션을 수행할 수 있다.

let map = new Map();
{
  let keyObject = { a: "b" };
  let valueObject = { b: "c" };
  map.set(keyObject, valueObject);
}
// 이 경우, 블록을 벗어나도 keyObject와 valueObject의 메모리는 반환되지 않는다.

let weakMap = new WeakMap();
{
  let keyObject = { a: "b" };
  let valueObject = { b: "c" };
  weakMap.set(keyObject, valueObject);
}
// 이 경우, 블록을 벗어나면 가비지 컬렉션이 수행될 때 안전하게 keyObject와 valueObject가 제거된다.