ECMAScript 6 Features

ECMAScript 6

git.io/es6features 을 기본으로 한글로 설명하였습니다. (기능들의 명칭은 별도로 번역하지 않았습니다.)

Introduction

새로운 Javascript 표준으로 알려져있는 ECMAScript 6 는 2015년 6월 개정을 목표로 하고 있다. Javascript 로 만들어진 여러 엔진들에 적용되고 있는데 링크 를 참조하도록 하자.

ECMAScript 6 의 전체적인 사양은 ES6 표준 초안 을 확인하도록 하자.

ES6에 도입된 달라진 점들:

ECMAScript 6 달라진 점들

Arrows

Arrows 는 => 함수를 짧게 표현하는 방식을 말한다. 이는 C#, Java 8 이나 CoffeeScript 과 문법적으로 유사하다고 한다. 일반적인 함수와는 다르게 this 를 호출해도 함수 자신을 뜻하는 것이 아니라 선언된 윗단계 스코프의 this 를 공유한다.

// Expression bodies
var odds = evens.map(v => v + 1);  
var nums = evens.map((v, i) => v + i);  
var pairs = evens.map(v => ({even: v, odd: v + 1}));

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

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

이해를 돕기 위해 위 예제를 기존 ECMAScript 5 의 구문으로 번역하여 보면 다음과 같이 표현할 수 있다.

// Expression bodies
var odds = evens.map(function (v) {  
  return v + 1;
});
var nums = evens.map(function (v, i) {  
  return v + i;
});
var pairs = evens.map(function (v) {  
  return { even: v, odd: v + 1 };
});

// Statement bodies
nums.forEach(function (v) {  
  if (v % 5 === 0) fives.push(v);
});

// Lexical this
var bob = {  
  _name: "Bob",
  _friends: [],
  printFriends: function printFriends() {
    var _this = this;

    this._friends.forEach(function (f) {
      return console.log(_this._name + " knows " + f);
    });
  }
};

Classes

ECMAScript 6 에서 가장 기대되고 있는 부분이 역시 Class 가 아닐까 한다. 포로토타입 기반 객체 지향 패턴의 설탕(Simple Sugar)과 같은 존재라고 표현했다. 어디다든 그냥 넣어서 먹을 수 있는게 설탕이듯 쉽고 편리하게 사용할 수 있어서 그렇게 표현한 것 같다. 생성자를 지원하고, 상속받은 객체의 메소드를 super 를 통해 부를수도 있고, 인스턴스, 정적 메소드 등등 을 지원한다고 한다.

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();
  }
}

Enhanced Object Literals

Object 구문이 향상되었다고 한다. 프로토타입도 생성시에 설정할 수 있고, foo: foo 와 같은 형태는 그냥 foo 라고 만 적어도 된다. 역시 클래스 처럼 상위 객체를 호출할 수 있고, 메소드 선언도 편리해졌거 하는 등등 의 장점이 있다. 결국 클래스가 도입되면서 그와 유사한 형태로 Object 를 변경한 것으로 보인다.

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
};

Template Strings

String 문법이 많이 좋아졌다. 특히 \n 을 사용해야 한줄을 띄워쓸 수 있었는데, 다른 언어들과 유사한 형태로 바뀐 것으로 보인다. 그리고 Interpolation 이 가능해진 것도 아주 다행스럽다. 변수 + ' 어쩌구 ' + 변수 이짓거리를 해본 사람은 알겠지... 태그를 넣어서 String 에 어떤 속성을 넣어줄 수 있는 것 같은데, 이건 해보기전에는 잘 모르겠다.

// 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
GET`http://foo.org/bar?a=${a}&b=${b}  
    Content-Type: application/json
    X-Credentials: ${credentials}
    { "foo": ${foo},
      "bar": ${bar}}`(myOnReadyStateChangeHandler);

Destructuring

Destructuring 부분이 가장 확연하게 차이나는 부분이라고 생각한다. 이것때문에 이 글을 쓰고 있다능... 변수 바인딩에서 패턴을 응용할 수 있다는 이야기인 것 같다.

그냥 보기는 어려워서 역시 ECMAScript 5 형태로 바꾸어 비교해보았다.

list matching
// ECMA Script 6
var [a, , b] = [1,2,3];

// ECMA Script 5
var _ref = [1, 2, 3];  
var a = _ref[0];  
var b = _ref[2];  
object matching
// object matching

// ECMA Script 6
var { op: a, lhs: { op: b }, rhs: c }  
       = getASTNode()

// ECMA Script 5
var _getASTNode = getASTNode();  
var a = _getASTNode.op;  
var b = _getASTNode.lhs.op;  
var c = _getASTNode.rhs;
object matching shorthand
// binds `op`, `lhs` and `rhs` in scope

// ECMAScript 6
var {op, lhs, rhs} = getASTNode()

// ECMAScript 5
var _getASTNode2 = getASTNode();  
var op = _getASTNode2.op;  
var lhs = _getASTNode2.lhs;  
var rhs = _getASTNode2.rhs;  
in parameter position
// ECMA Script 6
function g({name: x}) {  
  console.log(x);
}
g({name: 5})

// ECMA Script 5
function g(arg) {  
  var x = arg.name;
  console.log(x);
}
g({ name: 5 });  
Fail-soft destructuring

Destructuring 으로 변수를 선언할때 값이 없으면 undefined 가 입력되고 default 값을 넣어줄 수도 있다. Object 에서 foo["bar"] 에 아무런 값이 없으면 undefined 를 보여주는 것과 유사하다고 생각하면 된다.

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

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

Default + Rest + Spread

함수의 선언시에 다양한 방법으로 Argument 를 입력할 수 있게 되었다. 디폴트 값을 입력해줄 수 있다. 특히 독특한 점은 Rest 와 Spread 라는 개념인데, Rest 는 Arguments 의 수를 예상할 수 없을때 사용할 수 있고, Spread 하여 값을 사용할 수 있다. 말로 설명하려니 좀 난해한데, 아래 예제를 참고하자.

default parameter values
function f(x, y=12) {  
  // y is 12 if not passed (or passed as undefined)
  return x + y;
}
f(3) == 15  
rest & spread
function f(x, ...y) {  
  // y is an Array
  return x * y.length;
}
f(3, "hello", true) == 6  
function f(x, y, z) {  
  return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6  

Let + Const

블록 스코프 안에서 var 를 대체할 수 있는 letconst 가 도입되었다. let 으로 선언한 함수는 현재 block scope 안에서만 작동하고, const 는 상수처럼 값을 변경할 수 없다.

function f() {  
  {
    let x;
    {
      // okay, block scoped name
      const x = "sneaky";
      // error, const
      x = "foo";
    }
    // error, already declared in block
    let x = "inner";
  }
}

Iterators + For..Of

CLR IEnumerable 이나 Java Iterable 과 같은 특수한 반복문을 사용할 수 있게 되었다. for..of 구문이 새로 생기면서 가능해졌는데, 이 역시 뭐라 설명하기 난해하다. 예제를 참조...

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);
}

반복문은 아래의 덕타입 인터페이스로 만들어졌다고 한다. 일종의 약속된 행위를 정의해둔 것? 여기를 참조하자. 모질라 짱짱맨!!

interface IteratorResult {  
  done: boolean;
  value: any;
}
interface Iterator {  
  next(): IteratorResult;
}
interface Iterable {  
  [Symbol.iterator](): Iterator
}

Generators

Geneartors 는 iterator-authoring을 function* 와 yield 를 통해서 단순화 시킨다고 하는데, 이부분은 아직 모르겠어서 패스한다.

Generators simplify iterator-authoring using function* and yield. A function declared as function* returns a Generator instance. Generators are subtypes of iterators which include additional next and throw. These enable values to flow back into the generator, so yield is an expression form which returns a value (or throws).

Note: Can also be used to enable ‘await’-like async programming, see also ES7 await proposal.

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);
}

The generator interface is (using TypeScript type syntax for exposition only):

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

Unicode

고정 공백 유니코드를 지원한다. 기타 등등.

Non-breaking additions to support full Unicode, including new Unicode literal form in strings and new RegExp u mode to handle code points, as well as new APIs to process strings at the 21bit code points level. These additions support building global apps in JavaScript.

// 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);
}

Modules

주로 CommonJS 등에서 사용하는 모듈 패턴을 언어 차원에서 지원하게 되었다. 이부분은 아무래도 node.js 의 영향이 있었던 듯 싶다.

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

export defaultexport * 도 사용할 수 있다. 아래 참조:

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

Module Loaders

Module loaders 이 지원하는 개념:

  • Dynamic loading
  • State isolation
  • Global namespace isolation
  • Compilation hooks
  • Nested virtualization

모듈을 불러올때 다양한 설정을 추가할 수 있게 되었다. 뭐라 설명하긴 어렵지만, 여튼 module 패턴을 적용하면서 만들어진 내용으로 보인다.

// 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

데이터 스트럭쳐를 효율적으로 만들기 위해서 일반적으로 많이 쓰이는 알고리즘을 적용했다.

// Sets
var s = new Set();  
s.add("hello").add("goodbye").add("hello");  
s.size === 2;  
s.has("hello") === true;  

array 와 유사하지만 중복 방지

// Maps
var m = new Map();  
m.set("hello", 42);  
m.set(s, 34);  
m.get(s) == 34;  

일반적인 map 사용 방식과 같음. 다양한 key 를 설정할 수 있다.

// Weak Maps
var wm = new WeakMap();  
wm.set(s, { extra: 42 });  
wm.size === undefined

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

WeakMap 과 WeakSet 은 좀 다른 방식을 사용하는 것 같은데, leak-free 라고 한다. 뭔지 잘 모르겠음.

Proxies

Proxies enable creation of objects with the full range of behaviors available to host objects. Can be used for interception, object virtualization, logging/profiling, etc.

// 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!';  
// 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';  

There are traps available for all of the runtime-level meta-operations:

var handler =  
{
  get:...,
  set:...,
  has:...,
  deleteProperty:...,
  apply:...,
  construct:...,
  getOwnPropertyDescriptor:...,
  defineProperty:...,
  getPrototypeOf:...,
  setPrototypeOf:...,
  enumerate:...,
  ownKeys:...,
  preventExtensions:...,
  isExtensible:...
}

Symbols

Symbols enable access control for object state. Symbols allow properties to be keyed by either string (as in ES5) or symbol. Symbols are a new primitive type. Optional name parameter used in debugging - but is not part of identity. Symbols are unique (like gensym), but not private since they are exposed via reflection features like Object.getOwnPropertySymbols.

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  

Subclassable Built-ins

In ES6, built-ins like Array, Date and DOM Elements can be subclassed.

Object construction for a function named Ctor now uses two-phases (both virtually dispatched):
- Call Ctor[@@create] to allocate the object, installing any special behavior - Invoke constructor on new instance to initialize

The known @@create symbol is available via Symbol.create. Built-ins now expose their @@create explicitly.

// 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

Many new library additions, including core Math libraries, Array conversion helpers, String helpers, and Object.assign for copying.

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) })  

Binary and Octal Literals

2진 b, 8진 o 구분자가 추가되었다.

0b111110111 === 503 // true  
0o767 === 503 // true  

Promises

Promises 가 적용되었다. 만세!

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)]);
})

Reflect API

Full reflection API exposing the runtime-level meta-operations on objects. This is effectively the inverse of the Proxy API, and allows making calls corresponding to the same meta-operations as the proxy traps. Especially useful for implementing proxies.

// No sample yet

Tail Calls

Calls in tail-position are guaranteed to not grow the stack unboundedly. Makes recursive algorithms safe in the face of unbounded inputs.

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)  

Seokjun Kim

Read more posts by this author.

Subscribe to Make It Yourself

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!
comments powered by Disqus