ECMAScript 6 (ECMAScript 2015)

ECMAScript 6

이전 포스트 ECMAScript와 Babel 에서 ECMAScript 2015+ 의 Javascript 코드를 런타임 환경에 적용하기 위해서는 그에 맞는 버전으로 호환시켜줘야 하며 이를 위한 툴체인으로 Babel 이라는 것이 있다는 것까지 정리해보았습니다.

이번 포스트에서는 그렇다면 ECMAScript 2015에 어떤 syntax가 추가되었는지 까지 알아보는 시간을 가져보려합니다. 이번 주제를 위해 다양한 포스팅을 읽었고 그 중 principal 한 사이트를 번역하는 것이 도움이 될 것 같아 lukehobanes6features 를 번역 했습니다.

ECMAScript 6 git.io/es6features

Introduction

ECMAScript 2015 또는 ECMAScript 6는 ECMAScript 표준의 가장 최신 버전입니다. ES6는 2009년 ES5가 표준화된 이후 중대한 업데이트이자 첫번째 업데이트입니다. 주요 Javascript 엔진에서 이러한 기능의 구현은 현재 진행 중입니다.

ECMAScript 6 언어의 전체 사양은 ES6 표준을 참조하세요.

ES6는 다음의 새로운 기능을 포함합니다.

ECMAScript 6 기능

Arrows

화살표는 => 문법을 사용하는 함수 단축어이며 문법적으로 C#, Java 8 and CoffeeScript에 있는 관련된 기능과 유사하다. 그들은 함수 구문 블록 본문 뿐만 아니라 함수식의 값을 반환하는 구문 본문 둘다 지원한다. 함수와 달리 화살표는 주변 코드와 동일한 ‘lexical this‘를 공유한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Expression bodies
var odds = evens.map(v => v + 1); // [1, 3, 5, 7, 9]
var nums = evens.map((v, i) => v + i); // [0, 3, 6, 9, 12]
var pairs = evens.map(v => ({even: v, odd: v + 1}));
/*
pairs's value
[
{even: 0, odd: 1}
{even: 2, odd: 3}
{even: 4, odd: 5}
{even: 6, odd: 7}
{even: 8, odd: 9}
]
*/

// Statement bodies
nums.forEach(v => {
if (v % 5 === 0)
fives.push(v);
});
// [0, 0, 0]

// Lexical this
var bob = {
_name: "Bob",
_friends: [],
printFriends() {
this._friends.forEach(f =>
console.log(this._name + " knows " + f));
}
}
// bob._friends.push('karl', 'Tom')
// bob.printFriends()
// > Bob knows karl
// > Bob knows Tom

자세한 정보: MDN Arrow Functions

클래스 (Classes)

ES6 클래스는 prototype 기반의 OO 패턴을 넘어 직관적이다. 단일의 편리한 선언적 폼을 갖는다는 것은 클래스 패턴을 더 쉽게 이용할 수 있도록 하며 상호운용성을 독려한다. 클래스는 prototype 기반의 상속(inheritance), super call, instance and static methods and constructors을 지원한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SkinnedMesh extends THREE.Mesh {
constructor(geometry, materials) {
super(geometry, materials);

this.idMatrix = SkinnedMesh.defaultMatrix();
this.bones = [];
this.boneMatrices = [];
//...
}
update(camera) {
//...
super.update();
}
get boneCount() {
return this.bones.length;
}
set matrixType(matrixType) {
this.idMatrix = SkinnedMesh[matrixType]();
}
static defaultMatrix() {
return new THREE.Matrix4();
}
}

더 자세한 정보: MDN Classes

강화된 객체 리터럴 (Enhanced Object Literals)

객체 리터럴은 생성 시 prototype 설정, foo: foo 할당에 대한 약축, 메서드 정의, super calls 생성 그리고 함수식으로 속성 이름을 계산하는 것을 지원하도록 확장됩니다. 또한, 이러한 것들은 객체 리터럴과 클래스 선언을 더 가깝게 만들고, 객체 지향 디자인에서 동일한 편의성을 활용할 수 있게 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
[ 'prop_' + (() => 42)() ]: 42
};

자세한 정보: MDN Grammar and types: Object literals

템플릿 문자열(Template Strings)

템플릿 문자열은 더 직관적으로 문자열을 구성하도록 하며 Perl, Python 그리고 이외의 언어에서 문자열 보간 기능(string interpolation features)과 유사합니다. 선택적으로, 문자 구성을 커스터마이징하거나 인젝션(Injection) 공격을 피하기 위해 또는 문자 컨텐츠에서 높은 수준의 데이터 구조를 구성하기 위해 태그를 추가할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Basic literal string creation
`In JavaScript '\n' is a line-feed.`

// Multiline strings
`In JavaScript this is
not legal.`

// String interpolation
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// Construct an HTTP request prefix is used to interpret the replacements and construction
POST`http://foo.org/bar?a=${a}&b=${b}
Content-Type: application/json
X-Credentials: ${credentials}
{ "foo": ${foo},
"bar": ${bar}}`(myOnReadyStateChangeHandler);

자세한 정보: MDN Template Strings

Destructuring (비구조화)

배열이나 객체의 매칭을 지원하는 비구조화는 패턴 매칭을 통해 바인딩할 수 있도록 하며, 표준 객체 조회 foo["bar"] 와 유사한 비구조화는 찾을 수 없는 경우, undefined 값을 생성하는 fail-soft 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// list matching
var [a, , b] = [1,2,3];
// a = 1
// b = 3

// Add Custom function
function getASTNode () {
return {op: 1, lhs: {op: 2}, rhs: 3}
}

// object matching
var { op: a, lhs: { op: b }, rhs: c }
= getASTNode()

// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
var {op, lhs, rhs} = getASTNode()

// Can be used in parameter position
function g({name: x}) {
console.log(x);
}
g({name: 5}) // 5

// Fail-soft destructuring
var [a] = [];
a === undefined; // ture

// Fail-soft destructuring with defaults
var [a = 1] = [];
a === 1; // ture

더 자세한 정보: MDN Destructuring assignment

Default 파라미터 + Rest 파라미터 + Spread 연산자

Default 파라미터란 현재 실행중인 함수가 평가하는 기본 파라미터 값이며 함수 호출에서 배열을 연속적 인수(argurement)로 변환해고, 후행(trailing) 파라미터를 배열에 바인딩합니다. Rest 파라미터는 인수(argurements)에 대한 필요성을 대체하고, 일반적인 경우들을 보다 직접적으로 다룹니다.

1
2
3
4
5
6
7
// Default Parameter
function f(x, y=12) {
// y is 12 if not passed (or passed as undefined)
return x + y;
}
f(3) == 15
// true
1
2
3
4
5
6
7
8
// Rest Parameter
function f(x, ...y) {
// y is an Array
// y is ["hello", true]
return x * y.length;
}
f(3, "hello", true) == 6
// true
1
2
3
4
5
6
7
// Spread Operator
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6
// true

자세한 정보: Default parameters, Rest parameters, Spread Operator

Let + Const

블럭 범위의 바인딩을 구성하며 let 은 새로운 var , const 는 상수입니다. 정적 제한은 할당 전에 사용을 방지합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function f() {
{
let x;
{
// okay, block scoped name
const x = "sneaky";
// error, const
x = "foo";
}
// error, already declared in block
let x = "inner";
}
}

자세한 정보: let statement, const statement

Iterators + For..Of

Iterator objects는 CLR IEnumerable 또는 Java Iterable과 유사한 커스텀 반복문(iteration)을 가능하게 합니다. for..infor..of 를 사용한 사용자가 정의한 반복기 기반의 반복문으로 일반화합니다. 배열을 구현할 필요가 없으며 LINQ와 같은 지연(lazy) 디자인 패턴이 사용 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next() {
[pre, cur] = [cur, pre + cur];
return { done: false, value: cur }
}
}
}
}

for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}

반복문은 이와 같은 duck 타입 인터페이스에 기초합니다. (오직 설명을 위해 TypeScript을 사용합니다.)

1
2
3
4
5
6
7
8
9
10
interface IteratorResult {
done: boolean;
value: any;
}
interface Iterator {
next(): IteratorResult;
}
interface Iterable {
[Symbol.iterator](): Iterator
}

자세한 정보: MDN for…of

Generators

Generator는 function* and yield 를 사용해 반복기 작성을 단순화 하고, 함수는 Generator 인스턴스를 반환하는 function *로써 선언됩니다.. Generator는 추가적인 nextthrow를 포함하는 반복기의 서브타입(subtype)이며, Generator 안에서 다시 돌아가기 위한 값이 될 수 있다. 그러나 yield 는 값을 반환하기 위한 또는 던지기(throw) 하기 위한 수식 양식(form) 입니다.

주의: 또한 async 프로그래밍과 같이 await 도 사용 가능합니다. ES7의 await 제안을 봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var fibonacci = {
[Symbol.iterator]: function*() {
var pre = 0, cur = 1;
for (;;) {
var temp = pre;
pre = cur;
cur += temp;
yield cur;
}
}
}

for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}

Generator 인터페이스는 (오직 설명을 위한 TypeScript type syntax를 사용했다):

1
2
3
4
interface Generator extends Iterator {
next(value?: any): IteratorResult;
throw(exception: any);
}

자세한 정보: MDN Iteration protocols

유니코드(Unicode)

문자열에서 새로운 유니코드 리터럴 폼과 코드 포인트를 다루기 위한 새로운 RegExp u 모드 뿐만 아니라 21bit 코드 포인트 레벨에서 문자열을 처리하기 위한 새로운 API 를 포함하는 모든 유니코드를 지원하기 위한 논-블로킹 추가입니다. 이러한 추가는 Javascipt에서 세계적인 어플리케이션 구축을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// same as ES5.1
"𠮷".length == 2

// new RegExp behaviour, opt-in ‘u’
"𠮷".match(/./u)[0].length == 2

// new form
"\u{20BB7}"=="𠮷"=="\uD842\uDFB7"

// new String ops
"𠮷".codePointAt(0) == 0x20BB7

// for-of iterates code points
for(var c of "𠮷") {
console.log(c);
}

자세한 정보: MDN RegExp.prototype.unicode

Modules

컴포넌트 정의를 위한 모듈을 위한 언어 수준에서 지원하며 인기있는 Javascript 모듈 로더(AMD, CommonJS)가 패턴을 코드화합니다. 런타임 실행은 호스트가 정의한 기본 로더에 의해 정의되고, 암시적 비동기 모델입니다. - 요청된 모듈들이 처리되고 사용가능해질 때까지 어떠한 코드도 실행되지 않는다.

1
2
3
4
5
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
1
2
3
// app.js
import * as math from "lib/math";
alert("2π = " + math.sum(math.pi, math.pi));
1
2
3
// otherApp.js
import {sum, pi} from "lib/math";
alert("2π = " + sum(pi, pi));

몇 가지 추가적인 기능으로는 export defaultexport *이 있다.

1
2
3
4
5
6
// lib/mathplusplus.js
export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.log(x);
}
1
2
3
// app.js
import ln, {pi, e} from "lib/mathplusplus";
alert("2π = " + ln(e)*pi*2);

자세한 정보: import statement, export statement

Module Loaders

Module Loader가 지원하는 것들:

  • 동적 로딩 (Dynamic loading)
  • 상태 격리 (State isolation)
  • 글로벌 네임스페이스 격리 (Global namespace isolation)
  • 컴파일 훅 (Compilation hooks)
  • 중첩된 가상화 (Nested virtualization)

기본 모듈 로더가 구성되어 있을 수 있으며 새로운 로더가 격리 또는 제한된 실행구문 안에서 코드를 불러오고 평가할 수 있도록 구성될수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Dynamic loading – ‘System’ is default loader
System.import('lib/math').then(function(m) {
alert("2π = " + m.sum(m.pi, m.pi));
});

// Create execution sandboxes – new Loaders
var loader = new Loader({
global: fixup(window) // replace ‘console.log’
});
loader.eval("console.log('hello world!');");

// Directly manipulate module cache
System.get('jquery');
System.set('jquery', Module({$: $})); // WARNING: not yet finalized

Map + Set + WeakMap + WeakSet

공통된 알고리즘을 위한 효율적인 데이터 구조이며 WeakMaps은 누수(leak)로부터 자유로운 object-key 구조의 side tables을 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Sets
var s = new Set(); // Set(0) {}
s.add("hello").add("goodbye").add("hello"); // Set(2) {"hello", "goodbye"}
s.size === 2; // true
s.has("hello") === true; // true

// Maps
var m = new Map(); // Map(0) {}
m.set("hello", 42); // Map(1) {"hello" => 42}
m.set("s", 34); // Map(2) {"hello" => 42, "s" => 34}
m.get("s") == 34; // true

// Weak Maps
var wm = new WeakMap(); // WeakMap {}
wm.set({ extra: 42 }, 42);
// WeakMap {{…} => 42} [[Entries]]0: {Object => 42}__proto__: WeakMap
wm.size === undefined // true

/ Weak Sets
var ws = new WeakSet(); // WeakSet {}
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set

자세한 정보: Map, Set, WeakMap, WeakSet

Proxies

Proxy는 호스트 객체에 사용 가능한 모든 동작 범위를 가진 객체를 생성할 수 있다. 가로채기(interception), 객체 가상화(object virtualization), 로깅/프로파일링(logging/profiling) 등에 대해 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
// Proxying a normal object
var target = {};
var handler = {
get: function (receiver, name) {
return `Hello, ${name}!`;
}
};

var p = new Proxy(target, handler);
p.world === 'Hello, world!'; // true
1
2
3
4
5
6
7
8
9
10
// Proxying a function object
var target = function () { return 'I am the target'; };
var handler = {
apply: function (receiver, ...args) {
return 'I am the proxy';
}
};

var p = new Proxy(target, handler);
p() === 'I am the proxy'; // true

모든 런타임 수준의 메타 작업을 사용 가능한 트랩이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var handler =
{
get:...,
set:...,
has:...,
deleteProperty:...,
apply:...,
construct:...,
getOwnPropertyDescriptor:...,
defineProperty:...,
getPrototypeOf:...,
setPrototypeOf:...,
enumerate:...,
ownKeys:...,
preventExtensions:...,
isExtensible:...
}

자세한 정보: MDN Proxy

Symbols

Symbol은 객체 상태에 접근 제어를 가능하게 하며 속성에 (ES5에서와 같이 ) string 또는 symbol로 키를 지정할 수 있습니다. Symbol은 새로운 원시(primitive) 타입입니다. 선택적으로 description 파라미터를 디버깅에서 사용합니다. - 속성의 일부분은 아닙니다. Symbol은 고유하지만 Object.getOwnPropertySymbols 와 같은 reflection 기능을 통해 노출되기 때문에 비공개가 아니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var MyClass = (function() {

// module scoped symbol
var key = Symbol("key");

function MyClass(privateData) {
this[key] = privateData;
}

MyClass.prototype = {
doStuff: function() {
... this[key] ...
}
};

return MyClass;
})();

var c = new MyClass("hello")
c["key"] === undefined

자세한 정보: MDN Symbol

Subclassable Built-ins

ES6에서는 Array, Date 그리고 DOM Element 와 같은 내장 기능(built-in)을 서브클래싱할 수 있으며 현재 Ctor 라는 이름의 함수에 대한 객체 구조는 두가지 측면으로 사용한다.

  • 특정 행위를 설치하기 위한 Ctor[@@create]를 객체에 배치하기 위해 호출한다.
  • 새로운 인스턴스에 초기화하기 위한 constructor를 선언한다.

알려진 @@create symbol은 symbol.create 를 통해 사용가능하고, 내장 기능들은 현재 암죽적으로 스스로의 @@create를 노출합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Pseudo-code of Array
class Array {
constructor(...args) { /* ... */ }
static [Symbol.create]() {
// Install special [[DefineOwnProperty]]
// to magically update 'length'
}
}

// User code of Array subclass
class MyArray extends Array {
constructor(...args) { super(...args); }
}

// Two-phase 'new':
// 1) Call @@create to allocate object
// 2) Invoke constructor on new instance
var arr = new MyArray();
arr[1] = 12;
arr.length == 2

Math + Number + String + Array + Object APIs

핵심적인 수학 라이브러리, 배열, 변환 헬퍼, 문자열 헬퍼 그리고 복사를 위한 Object.assign를 포함해 많은 새로운 라이브러리 추가.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false

Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2

"abcde".includes("cd") // true
"abc".repeat(3) // "abcabcabc"

Array.from(document.querySelectorAll('*')) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1, 2, 3].find(x => x == 3) // 3
[1, 2, 3].findIndex(x => x == 2) // 1
[1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"

Object.assign(Point, { origin: new Point(0,0) })

자세한 정보: Number, Math, Array.from, Array.of, Array.prototype.copyWithin, Object.assign

Binary and Octal Literals

binary(2진법)의 (b) 와 octal(8진법)의 (o)에 대해 두 개의 새로운 숫자 리터럴 폼이 추가되었다.

1
2
0b111110111 === 503 // true
0o767 === 503 // true

Promises

Promise는 비동기 프로그래밍 라이브러리이며 미래에 가능하도록 하는 일급 표현(representation)이며, 현존하는 많은 Javascript 라이브러리에서 사용되고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
})
}

var p = timeout(1000).then(() => {
return timeout(2000);
}).then(() => {
throw new Error("hmm");
}).catch(err => {
return Promise.all([timeout(100), timeout(200)]);
})

자세한 정보: MDN Promise

Reflect API

객체에 대한 런타임 수준의 메타 작업을 노출하는 전체 reflection API. Reflect API는 실제로 Proxy API의 반대이고, proxy 트랩으로서 같은 메타 작업에 따르는 콜들은 만들도록 해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const duck = {
name: 'Maurice',
color: 'white',
greeting: function() {
console.log(`Quaaaack! My name is ${this.name}`);
}
}

Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false

Reflect.ownKeys(duck);
// [ "name", "color", "greeting" ]

Reflect.set(duck, 'eyes', 'black');
// returns "true" if successful
// "duck" now contains the property "eyes: 'black'"

자세한 정보: MDN Reflect

Tail Calls

tail 포지션의 콜(call)은 스택이 무제한으로 증가하지 않도록 보장합니다. 무제한 입력에 대한 재귀 알고리즘을 안전하게 만듭니다.

1
2
3
4
5
6
7
8
9
function factorial(n, acc = 1) {
'use strict';
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}

// Stack overflow in most implementations today,
// but safe on arbitrary inputs in ES6
factorial(100000)