吉田 啓二

「進化を続ける JavaScript ~次世代言語のステキな機能と高速化の行方~」HTML5 Conference 2013 セッションレポート

2013年11月30日(土)に開催された「HTML5 Conference 2013」の、Mozilla Japan浅井智也さんによるセッション「進化を続ける JavaScript ~次世代言語のステキな機能と高速化の行方~」のセッション内容をご紹介します。

html5conference-report-javascript-01

JavaScriptの課題と改良への動き

現在のJavaScriptには、次のような課題があります。

  • 高速化がなかなかできない
  • クラス、モジュールがない
  • プロトタイプベースであるため、普段Javaなどでクラスを使っている人にとって、メソッド定義や継承がわかりにくい
  • メソッドとして呼び出したときはthisがそのメソッドのオブジェクトになるが、間違って関数として呼び出すとthisがwindowオブジェクトになるなど、thisの挙動を捉えるのが難しい
  • argumentsなど、ArrayのようでArrayでなくついarguments.slice()とかしてエラー原因になる、紛らわしいオブジェクトがある
  • コールバック地獄
  • 実行時エラーが多く、コンパイルエラーが少ないので、デバッグが非常に大変である

JavaScriptの歴史について詳しくは過去の講演資料をご覧ください。簡単に言えば次の資料の通りです。次期ECMAScript 6thでは大きく改定されるため、この改定内容はぜひ知っておく必要があります。

javascript2013-131130030521-phpapp01-page13

ECMAScript 6thの概要

目標

ECMAScript 6thの目標は、より開発・テストを行いやすくし、相互運用性を確保し、静的検証(コンパイルエラーの検出)を行える構文・言語仕様を提供することにあります。

実装・対応状況

FirefoxのSpiderMonkeyの実装範囲が比較的広く、V8がそれに続く状況になっています。機能によってはV8の方が先に入っていたり、上手く実装されているものもあります。IEは非常に安定した、広く使われている仕様が実装されており、加えて、グローバリゼーション、国際化といったビジネス系の仕様が積極的に提案、実装されています。TypeScriptは、対応範囲がクラスなどに絞られている印象があるのですが、比較的綺麗なコードが生成されます。その他にも、letやconstといったブロックスコープだけを使いたいのでしたらdefs.jsを、あるいはModuleのimport/export機能だけを使いたい場合はes6-module-loaderといったコンパイラも使うことができます。

Syntax Sugar

分割代入 (Destructuring)

これまでのJavaScriptでは代入文の左辺に変数を一つしか指定できませんでしたが、ECMAScript 6thでは左辺に配列やオブジェクトを指定することができるようになります。これにより、例えば、関数から配列やオブジェクトなどで複数の返り値を受け取り、それをそのまま変数に格納するといった処理を記述できるようになります。JSONから特定のデータを抜き出す、といったことを行うこともできます。

default & rest parameter

引数のデフォルト値を設定できたり(default parameter)、関数の残りの引数を配列で受け取ったり(rest parameter)することができます。

配列の内包表記 (Comprehensions)

for…ofなどを使って、直接、配列のリテラルを生成することができます。

Modularity

ブロックスコープ (let, const)

今までのJavaScriptにはブロックスコープがなく、グローバルスコープと関数スコープしかありませんでした。変数をブロックスコープに納めるために、今まではわざわざ関数を作らなければなりませんでしたが、ECMAScript 6thでは、let, constを指定するだけでブロックスコープ変数を作成することができます。

Class

待望のClassが使えるようになります。他のオブジェクト指向の言語と同じような形式でClassを記述することができます。constructorメソッドが初期化時の処理として使われます。

Module

今まで、モジュールをインポート・エクスポートする様々なライブラリが作られてきましたが、この機能が公式のものとして提供されることになりました。

Readable Code

Arrow Function

今まで、コールバックとして関数を渡す際に「function(…){…}」と記述する必要があり、ソースコードが冗長で見にくくなっていましたが、Arrow Functionを使うことでこの関数をシンプルに記載できるようになります。

Arrow Functionには、簡潔に書けるということに加えて、もう一つ良いことがあります。それは、thisが保持・バインドされるということです。今まで、コールバック関数内で呼び出し元のオブジェクトを参照しようとすると、thisがwindowオブジェクトを参照するようになるため、コールバック関数の前で「self = this」などと記載し、この場合はコールバック関数内で「self」を参照しなければなりませんでした。Arrow Functionですと、Arrow Function内のthisが、その外側の関数のthisを参照し続けるという特性があり、このような記載が不要となります。

Generator

nextメソッドを持ち、繰り返し処理が実行される際にnextメソッドが呼ばれる特殊なオブジェクトをイテレータ(iterator)と呼びます。イテレータを簡単に生成できるジェネレータ関数によって生成されたイテレータをジェネレータ(generator)と呼びます。ジェネレータ関数では、yieldというキーワードがreturnの代わりに使われており、yieldが実行されるとその引数が返却されて関数の処理が一時停止します。そして、再度関数が実行されるとyieldの次の行から処理が再開し、またyieldが実行されると引数を返却して処理が一時停止する、という処理の流れになります。ジェネレータ関数を使うことで、関数の再起呼び出しを行うことなく、何かしらの繰り返しの処理を行うことができます。

先ほどのfor…ofの中でジェネレータ関数を使う方法以外にも、毎回明示的にイテレートする方法があります。こちらがイテレータの本来の使い方なのですが、例えば、カウンタを作成する場合、毎回一時停止して内部で保持している値をインクリメントするジェネレータ関数を定義すると、nextメソッドを呼ぶたびにインクリメントされた値を取得する処理を実装することができます。

Promise

Promiseを使うことで、今までよりもコールバックを非常に簡潔に書けるようになります。また、catchなどの構文も今まで以上に書きやすくなります。Promiseを生成するときに引数として関数を渡し、そこでresolve関数とreject関数を指定します。このresolve関数が呼ばれたときにこのPromiseインスタンスが解決し、このインスタンスのthenメソッドが実行されます。

New Type & API

Collections

Set型、Map型が追加されます。Setを使うことで、集合に対して要素を追加したり参照したりすることができます。Mapは、キーとそれに対応する値を保持できるものです。配列やオブジェクトではキーとして文字列しか設定できませんでしたが、Mapではキーとしてオブジェクトを設定することができます。

ここまで次世代言語仕様の一部をご紹介してきましたが、このようにJavaScriptは構文や機能面でも大きな進化を遂げ、落とし穴が少なくシンプルな記法で書ける言語に進化するのです。
そして一方、広く使われる言語は書きやすいだけでなく高速でなければなりませんが、ここからは速度面の進化についてご紹介します。

Slow Parts & Fast Parts

JavaScriptが遅い原因として、JavaScriptが動的型付け言語であることや、JavaScriptにクラスと配列が存在しないことなどがありますが、これらの原因を回避して、JavaScriptエンジンが最適化しやすいコードだけを書くようにすることで、処理を高速化させることができます。例えば、型固定で変数を定義することや、一度定義したオブジェクトのプロパティの追加・削除を行わない、といったことなどに気をつければ、処理を高速化させることができます。このように、JavaScriptエンジンが最適化しやすく、高速化を図れるコードを、私の中で「JavaScript Fast Parts」と呼んでいます。

Typed Array

Fast Partsの代表的なものとして、Typed Arrayがあります。これを使うことで、今までJavaScriptではできなかった静的型付け・型の固定を行うことができます。

asm.js

以上のような高速化できる方法だけを使ってコードを書く、ということをあらかじめルールとして定めてしまい、JavaScriptエンジン側も最初から最適化された状態でコンパイルして高速に実行するようにしよう、という発想に基づいた高速化方法を、Mozillaがasm.jsとして提案しています。コードを自動生成するための様々なツールが存在していますが、これらは全て、JavaScriptエンジンが速く実行できるコードを生成しようとしています。このように、既存のエンジンにおける高速化のノウハウがある程度溜まっているため、それらのノウハウを結晶させたFast Partsを使って高速化を実現させるということが、asm.jsの発想です。

asm.jsの目的は、WebをNativeと同様の速度にすることです。Native同様の速度にするために、新しいコンパイルの方法を導入したりするわけではありません。現在のJavaScriptエンジンでも、最適化できるコードに関しては、既に型固定の高速なコードが生成されるようになっています。その際に、コード生成のためのオーバーヘッドや型変換時のチェックなどがあり、これらがなければNative並みの速度で実行されるようになりつつあります。このエンジンをそのまま利用して、今まで「このコードを最適化して良いか否か」をヒューリスティックに判別していたところを、asm.jsを利用すると、決め打ちで最適化できるようなります。

既存のJavaScriptエンジンでも、よく使われるコードを見つけて、それらを最適化することが幅広く行われています。しかし、簡単なコードは最適化できても、巨大なコードは最適化できない場合が多く、C言語よりも10倍、20倍遅い場合もあります。asm.jsを利用し、常に最適化するようにした場合は、asm.js導入直後の時点でもC言語の2倍の速度で済むレベルまで高速化されました。asm.jsでの高速化は、現在も改良が進められています。

html5conference-report-javascript-02

結論:Always bet on JavaScript!

Webは非常に互換性を重視しており、既存のものを動作させ続けながら、機能を追加したり速度を改善したりする必要があります。本日ご説明しました通り、JavaScriptでは、必要な機能の追加や高速化が順次行われているため、JavaScriptはこれまで同様に生き続け、皆さんが勉強する価値のある言語になっていると思っており、これからもJavaScriptを愛して頂ければ嬉しいです。


週間PVランキング

新着記事

Powered byNTT Communications

tag list

アクセシビリティ イベント エンタープライズ デザイン ハイブリッド パフォーマンス ブラウザ プログラミング マークアップ モバイル 海外 高速化 Angular2 AngularJS Chrome Cordova CSS de:code ECMAScript Edge Firefox Google Google I/O 2014 HTML5 Conference 2013 html5j IoT JavaScript Microsoft Node.js Polymer Progressive Web Apps React Safari SkyWay TypeScript UI UX W3C W3C仕様 Webアプリ Web Components WebGL WebRTC WebSocket WebVR