UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

ECMAScript2015(ES2015)を トランスコンパイル言語 として考えてみる

こんにちは、タナカです。
寒い季節はラーメンが旨いですね。最近は五行の焦がし味噌ラーメンがお気に入りです。

さて、先日の勉強会では「ECMAScript2015(ES2015)を トランスコンパイル言語 として考えてみる」という内容で発表をさせていただきました。

JSの開発環境の移り変わりは激しいもので1年前とはだいぶ状況が変わりました。
昨年、僕はCoffeeScript、bower、gruntにて開発をしておりましたがどれもオワコンとなりつつあります・・・。
JSのトランスコンパイラーとしては、TypeScriptやDartが注目されていますが、これから何が流行るのか見極めたいところです。
ただCoffeeScriptに慣れてしまった以上、いまさらJSをES5で書く気にはなれません。

しかし!!ECMAScript2015(ES2015)ならば、CoffeeScriptで便利だった機能がいくつか取り込まれておりES5と比べればかなり使いやすくなっています!
ES2015ならばオワコン化することも無いはずなので、ES2015をES5にトランスコンパイルしてくれるBabelを使いながらしばらく様子を見てみようと思っています。

ECMAScript2015(ES2015)とは?

ECMAScriptはJavaScriptの標準を定めたものですが、現在のところECMAScript2015(第6版、通称ES6)が最新バージョンです。 ClassやPromiseなど、ES5ではトランスコンパイラーやライブラリで実現してきた機能が標準で使えます。

Chrome、Firefox、Safari、Edgeの最新版のブラウザであれば概ね対応していますが、 残念ながら、IEやAndroidの旧ブラウザでは対応していません。

※各ブラウザの対応状況
http://kangax.github.io/compat-table/es6/

Babelとは?

BabelはES2015や、ES2016で書かれたJavaScriptファイルをES5相当にトランスコンパイルしてくれるものです。
ES2015の便利な機能を使いつつも、旧ブラウザへも対応できるという大変便利なツールです。

ES2015で書くと何が便利なのか?

ということで、ES2015で書くと何が便利になるのかまとめてみました。

アロー構文

=>がfunctionのショートカットとして使えます。 アロー構文で書くと、thisが定義したオブジェクトを指すようになりわかりやすいです。 CoffeeScriptと同様簡潔に書けます。

// Expression bodies
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);

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

クラス

classが簡潔に書けます。 CoffeeScriptと同様extendsも指定できます。

class SkinnedMesh extends THREE.Mesh {
  constructor(geometry, materials) {
    super(geometry, materials);

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

テンプレート文字列

文字列に変数を埋め込むことができます。 途中に改行を挟むこともできます。 こちらもCoffeeScriptとほぼ同様ですね。

// Basic literal string creation
`This is a pretty little template string.`

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

// Interpolate variable bindings
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// Unescaped template strings
String.raw`In ES5 "\n" is a line-feed.`

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

デフォルト値

引数にデフォルト値が指定できます。 こちらもCoffeeScriptで便利な機能のひとつでした。

function f(x, y=12) {
  // y is 12 if not passed (or passed as undefined)
  return x + y;
}

f(3) == 15

可変長引数

可変長引数も使えます。

function f(x, ...y) {
  // y is an Array
  return x * y.length;
}
f(3, "hello", true) == 6

分解代入

配列やオブジェクトの内容を分解して受けることができます。 関数の引数に使えば、引数を名前指定で渡せます。

// list matching
var [a, ,b] = [1,2,3];
a === 1;
b === 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})

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

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

a === 1;

// Destructuring + defaults arguments
function r({x, y, w = 10, h = 10}) {
  return x + y + w + h;
}
r({x:1, y:2}) === 23

新しい宣言文

変数と定数用の新しい宣言が用意されました。 こちらはCoffeeScriptには無かった機能です。 letやconstで宣言したものはvarとは違ってブロックスコープとなりますが、Babelではvarに置き換わるので関数スコープのままです。

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

Modules

モジュールが使えます。 node.jsやcommon.jsのrequireのように書けます。

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

文字列の新メソッド

includes()、repeat()が追加されました。

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

配列の新メソッド

fill()等の便利なメソッドが追加されました。

[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["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"

Promises

Promiseが標準で使えるようになりました。 非同期処理でのコールバック地獄を解消してくれます。

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

まとめ

ES2015のアロー構文やクラス等を利用することでコードを簡潔に書けるようになります。
他のトランスコンパイル言語と比較すると、機能的には見劣りしてしまいますがES5と比べるとかなり魅力的です。
ES2015はトランスコンパイル言語としても十分使えるものではないでしょうか。

今回紹介した内容はES2015のごく一部の機能で他にもたくさんの機能があります。 Babelの公式サイトにわかりやすくまとめられていますので、ぜひチェックしてみてください。
このページのサンプルコードもBabelの公式サイトからお借りしたものです。

Babel公式サイト Learn ES2015
https://babeljs.io/docs/learn-es2015/