Domenic Denicola氏、JavaScriptの今を紹介 ~東京Node学園祭2015 基調講演

2015年11月7日、株式会社サイバーエージェントセミナールームにて東京Node学園祭2015が開催されました。本稿では、基調講演の模様をレポートします。

画像

基調講演はECMAScriptを策定している委員会TC39のメンバーのDomenic Denicola氏です。PromiseやStreamの仕様策定に携わっていることで有名です。また日本でもよく使われているjsdomの作者でもあります。

今回の発表は、JavaScriptの辿った歴史から現在のECMAScript2015(以下ES2015⁠⁠、そして未来について包括的なものとなりました。

画像

スライドはSlideShareにて公開されています。

The State of JavaScript (2015) from Domenic Denicola

JavaScriptの歴史

JavaScriptは今から約20年前の1995年にNetscapeで実装されたのがはじまりで、1997年にECMAScriptとして標準化され、現在に至ります。

2005年にはAjaxが登場し、翌年2006年にjQueryが広く使われるようになりました。2008年にはNode.jsのエンジンでもあるV8が登場し、JavaScriptのエンジン間で速度競争がはじまりました。2009年はNode.jsの誕生、ECMAScript5が策定され今日のウェブを支えています。

2012年以降はNode.jsを搭載したロボットなどハードウェアや、asm.jsWebAssemblyでより低いレイヤのAPIを扱えたりと広がりを見せています。

画像

JavaScriptの現在

今年の6月にES2015が策定され、各ブラウザやランタイムがこの仕様に即した実装をしています。

ES2015の中でも大きな変更はシンタックスです。中でも、クラス糖衣構文class, extends, superやアロー関数arr.map(x => x * x)⁠、ブロックスコープ{ let x = 5; const y = 10; }はNode.jsで既に実装されており、今日から使うことができます。分割代入var {x, y} = getPoint()⁠、レスト/スプレッドvar [first, ...rest] = els; Math.max(...myArray)⁠、デフォルトパラメータfunction parseInt(x, base = 10) { }はまだ使えません。

画像

次に大きな変更はデータの構造です。マップとセットはO(1)でアクセスできるオブジェクトです。また、ウィークマップ/ウィークセットはライブラリ提供者によりプライベートな名前空間を提供できます。そして、for-of構文によってオブジェクトでも配列でも同じ構文で書くことが可能になりました。これらはNode.jsでもう使うことができます。

画像

最後に紹介したのは、これからのJavaScriptを大きく変える革新的な新機能です。ジェネレータとPromiseは非同期処理を書くための機能です。これまでのJavaScriptは非同期処理を様々な方法で処理していましたが、今後は標準化されたこれらの機能で統一されていくでしょう。プロキシはオブジェクトのメソッドに任意の処理を挟むことができる機能で、ログ出力やプロファイルが想定されています。テンプレートストリングjsx`<a href="${url}">${text}</a>`は、文字列に変数を挿入できる構文です。中でも最も重要な機能はビルトインオブジェクトをサブクラスとして使えるようになったことですclass Elements extends Array { }⁠。これによって、これまで配列ではなかったDOMのリストなども配列として扱えるように現在実装が進められています。これらの内でプロキシ以外はNode.jsで使うことができます。

画像

実際のプロジェクトでもvarは使わずにlet/constを使っていたり、シンボルやアロー関数を使っている様子もスニペットと共に紹介しました。ESLintを用いて新しい構文を使うように強制しています。非同期処理もジェネレータとPromiseによって見通しよく書くことはとても楽しいそうです。

まだES2015の機能が実装されていないブラウザや、標準化されていない構文のためにBabelを使っています。BabelはES2015や、標準化されていない構文を未実装のブラウザ等で動作させるためのトランスパイラです。Babelは新しい構文を試すことに向いていますが、標準化されていない構文を使う際には将来変更されることを考慮して書くようにしてください。

ブラウザでの新たな機能

ここまではJavaScriptの構文を取り上げてきましたが、ブラウザで新たな機能も実装されているので"Node"学園祭ですがこれらについても紹介しました。この背景として、現在大半がC++で書かれている機能をJavaScriptに低レベルのAPIとして提供することを目的とするThe Extensible Web Manifesto(日本語訳)があります。これによって今までJavaScriptではできなかったことができるようになってきたと言います。

まずはJavaScriptで任意のHTML要素を定義できるカスタムエレメンツです(リスト1⁠⁠。これはGoogle Chromeでは以前から提供されている機能でしたが、ようやく他のブラウザでも実装がはじまりました。

リスト1 カスタムエレメント
class CustomImage extends HTMLElement {
  constructor(src) {
    super();
    if (src !== undefined) this.src = src;
  }

  get src() {
    return (new URL(this.getAttribute("src"), this.baseURI)).href;
  }

  set src(value) {
    this.setAttribute("src", value);
  }

  [Element.attributeChanged](name) {
    if (name === "src") updateShadowDOM(this, this.src);
  }
}

document.registerElement("custom-image", CustomImage);

次はサービスワーカです。サービスワーカの例としてToolboxというライブラリを紹介しました。これはサービスワーカでキャッシュサーバを作り、通信のキャッシュを制御するライブラリで、これによってよりサーバとクライアントでより近い開発ができるようになります(リスト2⁠⁠。

リスト2 サービスワーカ
toolbox.precache(['index.html', '/site.css', '/images/logo.png']);

toolbox.cache('/data/2014/posts.json');
toolbox.uncache('/data/2013/posts.json');

toolbox.router.get('/:foo/index.html', (req, values) => {
  return new Response(
    `Handled a request for ${req.url}
    with :foo as ${value.foo}`);
});

toolbox.router.get('/myapp/a', toolbox.networkFirst);
toolbox.router.get('/myapp/b', toolbox.networkOnly);
toolbox.router.get('/myapp/c', toolbox.cacheFirst);
toolbox.router.get('/myapp/:page', toolbox.cacheOnly);
toolbox.router.get('/(.*)', toolbox.fastest);

最後はカスタムペイントです。これまでCSSによる描画はJavaScriptから制御しづらいものでしたが、カスタムペイントでは新たなCSSプロパティを追加できます。カンバスのようなAPIでスタイルをすべて変えることができます(リスト3⁠⁠。

リスト3 カスタムペイント
registerPaint('circle', class {
  static get inputProperties() { return ['--circle-color']; }

  paint(ctx, geom, properties) {
    // 色を変える
    ctx.fillStyle = properties.get('--circle-color');

    // 中心点と半径を計算する
    const x = geom.width / 2;
    const y = geom.height / 2;
    const radius = Math.min(x, y);

    // 円を描く
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
    ctx.fill();
  }
})

Domenic Denicola氏は「これらのブラウザでの新たな機能から、Node.jsでの開発のインスピレーションになれば幸いです」と述べています。

JavaScriptの未来

これからのJavaScriptを語る上で一番重要なことはECMAScriptの仕様がGitHubにホスティングされるようになったことです。これまではWordによって管理されていましたが、GitHubで管理することでリビングスタンダードとして参照され、誰でも貢献できるようになったのです。

次に大事なことは、仕様のバージョンがなくなったことです。言語の仕様で大事なことはランタイムやブラウザでその機能が実装されているかどうかということです。今後はバージョン番号を廃し、GitHub上に公開されているドキュメントがリビングスタンダードとして参照されるのです。このリビングスタンダードと違う場合、実装しているベンダにフィードバックすると良いでしょう。

そして、ES2015に含まれていないが既に誰かが取り組んでいる、もしくは準備ができているものを紹介しました。

まずはasync/awaitです。この構文はジェネレータにも似た構文ですが、ジェネレータよりも簡潔に書くことができます(リスト4⁠⁠。また、Microsoft EdgeやMozilla Firefoxでは既に使える構文です。

リスト4 async/await
async function getUserImages() {
  const response = await fetch("http://example.com/users");
  const users = await response.json();

  return Promise.all(users.map(async (u) => {
    return {
      name: u.name,
      image: (await fetch(u.imageUrl)).body
    };
  }));
}

次に紹介するのはSIMD.jsです。これはハードウェアで動作するときのためのもので、よりCPUに近いAPIを使うことができるため、さらなる高速化が可能となります(リスト5⁠⁠。

リスト5 SIMD.js
for (let i = 0; i < max_iterations; ++i) {
  const z_re24 = SIMD.float32x4.mul(z_re4, z_re4);
  const z_im24 = SIMD.float32x4.mul(z_im4, z_im4);

  const mi4    =
SIMD.float32x4.lessThanOrEqual(SIMD.float32x4.add(z_re24, z_im24),
four4);

  if (mi4.signMask === 0x00) {
    break;
  }

  const new_re4 = SIMD.float32x4.sub(z_re24, z_im24);
  const new_im4 = SIMD.float32x4.mul(SIMD.float32x4.mul(two4, z_re4), z_im4);
  z_re4         = SIMD.float32x4.add(c_re4, new_re4);
  z_im4         = SIMD.float32x4.add(c_im4, new_im4);
  count4        = SIMD.int32x4.add(count4, SIMD.int32x4.and(mi4, one4));
}

ここまでは既に誰かが取り組んでいるものを示しましたが、これ以降はコンセプト段階だけれども面白い構文を紹介しました。

1つめはバリュータイプです。これはメモリを効率よく活用するための構造体を定義する構文や、CSSで使われる単位をリテラルとして付加する構文などが含まれています(リスト6)。

リスト6 バリュータイプ
// 64ビット整数
const fifteen = 5UL + 10UL;

// メモリ効率の良い構造体
const Point = new ValueType({ x: float64, y: float64 });
const point = new Point({ x: 1.5, y: 2.4 });
assert(point === new Point({ x: 1.5, y: 2.4 }));

// カスタムリテラルとオペレータオーバーロード
const romaineLettuce = 0x3B5323FFrgba;
const length = 50percent + 10em + 5px;
el.css.width += 2rem;

2つめはデコレータです。これはメソッドを任意のメソッドに変更する構文で、元々AngularJSで使われている構文です(リスト7⁠⁠。リスト7ではパフォーマンスの必要なメソッドに対してデコレータで実行時間を測定するように変更しています。

リスト7 デコレータ
class BusinessLogic {
  @performance
  doImportantComputation() {

  }
}

(new BusinessLogic()).doImportantComputation();
// => doImportantComputation: 102ms

3つめはキャンセル可能Promiseです。これは通常のPromiseがどのような状態でもその動作をキャンセルできる機能です(リスト8⁠⁠。リスト8は、複雑な処理をするPromiseをキャンセルボタンを押すことでキャンセルするような例です。

リスト8 キャンセル可能Promise
startSpinner();

const p = fetch(url)
  .then(r => r.json())
  .then(data => fetch(data.otherUrl))
  .then(res => res.text())
  .then(text => updateUI(text))
  .catch(err => showUIError(err))
  .finally(stopSpinner)

cancelButton.onclick = () => p.cancel();

4つめは非同期イテレータです。これはasync/await構文とfor-ofを組み合わせたもので、Node.jsのStreamの処理などがこれによって書き換えられるコードの代表例です(リスト9⁠⁠。

リスト9 非同期イテレータ
async function* directoryEntries(path) {
  const dir = await opendir(path);

  try {
    let entry;
    async for (const entry of readdir(dir)) {
      yield path.resolve(entry);
    }
  } finally {
    await closedir(dir);
  }
}

5つめはモジュールローディングです(リスト10⁠⁠。最近ホットな話題はモジュールロードですが、仕様レベルでいうとまだ何も決まっていないというのが実情です。リスト10に示すコードが最小の機能だと考えていて、これで合意がとれれば仕様策定を進めていけると考えています。

リスト10 モジュールローディング
<script type="module">
import a from "./a.js";
import { b } from "../b.js";
import $ from "https://cdn.example.com/jquery.js";
</script>

そして最後はWebAssemblyです。これはまだ謎に包まれていて、まだ全貌が分かっていません。現状は、Google, MicrosoftやAppleもエンジニアを投入していて新たなバーチャルマシンを作っているようです。C++やJavaなどからコンパイルして使うことを想定して作っているようです。GitHub上でドキュメントを読むことができます。

まとめ

JavaScriptはその20年の歴史の中でも今が1番大きな転換点を迎えているといっても過言ではないでしょう。そのような中でDomenic Denicola氏は、これまでの歴史とこれからについて構文の紹介や新機能の紹介を、スニペットを交えて講演しました。

おすすめ記事

記事・ニュース一覧