ECMAScript 6でモダンWebアプリケーションへ向かって
こんにちは、ゼノフィnakamuraです。
ECMAScript(我々がJavaScriptと認識している言語の正式名)はこの2年間に渡って、素晴らしく成功してきました。
収束してきた標準サポート、モダンなJavaScriptエンジンによるパフォーマンス改善、さらにはサーバーサイドスタックへの進出と、ECMAScriptは強大な推進力を得て、HTML5アプリケーションの範囲を再定義しました。
世界の優位に立つための最後の条件は構文とランタイムの現代化で、それはECMAScript 6で登場することにななります。
そしてそれが、このポストのテーマとなります。
ECMAScript 6での言語的な進歩を理解するために、最初にECMAScriptの発展の経緯を理解する必要があります。 公式のECMAScript仕様(ECMA-262としても知られている)は Ecma International という基準団体で定義されています。 ECMA内の TC39 グループには言語仕様をマニュアル化する役割があります(TCはTechnical Committeeの略です)。 この委員会には、各ブラウザベンダーやこの規格に興味ある他の団体からの代表が一人か一人以上います。 これらのメンバーには言語の次バージョンの機能と追加点を擁護する責任があります。 時折、TC39のメンバーは最近の進捗をレビューし、公式の仕様を決定するために会議します。 たびたびes-discussのメーリングリストを使って、一般人とアイデアを話し合うこともあります。
ECMAScript 6の仕様はまだドラフトの状態です。 これまでにたくさんの ドラフトリビジョン があり、最新のものは7月15日に公開されました。 このポストで紹介しているコンセプトやサンプルは執筆時点での関連する解説は最新仕様のドラフトに従っていますが、 仕様の最終決定までの近い将来の間に構文や機能は変わる可能性があります。
“はっきり見えるよ”
よく「コードは書くのは一回だけど、何度も読まれる」と言いますね。 実は、コードレビューに関わっている私達は、明確でで曖昧さのないコードの重要さはよく知っています。 さらに、明確な構文もあると新しいチームメンバーも簡単にコードを理解し、よくある落とし穴を避ける事ができます。
初心者にとっての良くある謎は次の構文:
1 2 3 4 5 | // ES 5 function inc(x, y) { y = y || 1; return x + y; } |
2つ目の引数”y”の使い方に混乱します。
その値は定数の1がある合理的なORを含めているから、もしyがundefined
だったら(より正確に言うとyがfalsey
であれば)、定数の1の値をセットします。
例えば、もしこの関数が、inc(4)
のように、1つの引数で呼び出されたら、返される値は5(4+yの規定値の1)になります。
このような賢く見えるハックはバグが出やすく(yに0に渡すと間違った結果になります)、エラーが出やすく(論理的なORの代わりにビット演算子のORが使われたらどうなりますか)、またこのテクニックになじみのない人にとっては完全に無意味です。
この問題を解決するための他の違ったやり方もありますが、問題は変わりません:これらは実行時の作業になります。
ECMAScript 6ではデフォルトのパラメータ値を設定する組み込みの機能があり、この問題を解決します。 前の構文を次の用法で書きかえることができ、これは他のプログラミング言語をご存知の方にはよく理解できると思います:
1 2 | // ES 6 function inc(x, y = 1) { return x += y; } |
初心者にとってもう一つの落とし穴は、変数のスコープがブロックスコープではなく関数スコープだという事実です。 既に他の”{ }”を使うプログラミング言語に慣れている人にとっては、この違いにびっくりするかもしれません。 さらに、変数の巻き上げはその状況をさらに混乱させます。なにせ変数は初期化されてないのにスコープに存在するかもしれないからです。
次のサンプルをご覧ください。条件分岐の内側の変数”N”は外側の”N”と同じものです。Nは関数スコープなので、if構文の内部で新しいNを宣言しても効果はありません:同じNなので、オリジナルの値が上書きされます。
1 2 3 4 5 6 7 8 9 | // ES 5 function doSomething() { var N = 5; if (someCondition) { var N = 10; doSomethingElse(N); } console.log(N); // 10 } |
これは簡単な例なので、問題はすぐに発見されます。 しかし、クロージャをいくつか利用するような大きいコードブロックに、このようなずさんな名前の競合があると発見しにくいバグにつながるかもしれません。
ECMAScript 6ではスコープが一番近い中括弧内に囲まれている変数や関数となるレキシカルブロックスコープの概念を導入します。上の例の問題は新しいlet
キーワードを利用してエレガントに解決できます。
1 2 3 4 5 6 7 8 9 | // ES 6 function doSomething() { let N = 5; if (someCondition) { let N = 10; doSomethingElse(N); } console.log(N); // 5 } |
constはletの兄弟のようなもの。これはブロックスコープも含めてletと同じように動作しますが、初期値が必要となります。名前が示すように、constは定数のバインディングなので、その値を替えても効果はありません:
1 2 3 4 | // ES 6 const limit = 100; limit = 200; console.log(limit); // 100 |
const
の主なユースケースは重要な定数を間違えて上書きされないように守る事です。
ECMAScript 6にはコーディングの儀式的な部分を減少するための新たな機能もあります。簡略メソッド定義(Concise method definition)はオブジェクト内で関数を定義するコンパクトな方法を提供し、プロパティを定義する方法と対称になります。次の例で示すように、function
キーワードを利用する必要がなくなります。
1 2 3 4 5 | // ES 6 var Person = { name: 'Joe', hello() { console.log('Hello, my name is', this.name); } }; |
コールバックとなる関数(オブジェクトの一部ではないもの)はarrow関数の利用によって、ショートカット表記を利用できます。例えば、次のコードはある数字の平方を計算します:
1 2 3 4 | // ES 5 [1,2,3].map(function (x) { return x * x; }); |
ECMAScript 6ではより短く書けます:
1 2 | // ES 6 [1,2,3].map(x => x * x); |
arrow関数(=>)を使うと、もうfunctionやreturnキーワードは必要なくなります。関数ボディ内の最後の式が返す値となります。注:関数が2つか以上のパラメータを受け入れる必要がある場合は、パラメータを括弧で囲みます。
ECMAScript 6の新しいスプレッド演算子(spread operator)は実行時のボイラープレートコードを削減するために使われます。経験深い開発者はapply
を色々な形で利用するのはよくあることなので、関数の引数の一覧として、配列が利用される必要がある時がよくあります。このような利用の例は次のような感じです:
1 2 | // ES 5 var max = Math.max.apply(null, [14, 3, 77]); |
ただ、このような混乱しやすい構文は今後普通ではなくなります。名前が表すように、スプレッド演算子 “…”は渡された配列をその関数の為に正しい引数として分割します。
1 2 | // ES 6 var max = Math.max(...[14, 3, 77]); |
スプレッド演算子のもう一つの使い道はrestパラメータのためです。これは上の見本の逆になります:関数の引数を配列に切り替える必要があります。この重要性を理解するために、次のコードをご覧ください:
1 2 3 4 5 6 7 8 9 | // ES 5 store('Joe', 'money'); store('Jane', 'letters', 'certificates'); function store(name) { var items = [].slice.call(arguments, 1); items.forEach(function (item) { vault.customer[name].push(item); }); } |
ここには銀行の中に金庫があって、対するお客様の貸金庫に無限の所有物を保管します。このようなAPIでは、store関数は特別なオブジェクトargumentsに魔法のような呪いをかける必要があります。こうすると、渡された所有物を配列に変換し、その配列要素を正しい場所にプッシュできます。 これは明確というところからかけはなれていて、コードの臭いがします。
ECMAScript 6でrestパラメータのためにスプレッド演算子を使うことで、このような実行時の苦労はなくなります。 次のコードはECMAScript 6で書き直したものです。
1 2 3 4 5 6 7 8 | // ES 6 store('Joe', 'money'); store('Jane', 'letters', 'certificates'); function store(name, ...items) { items.forEach(function (item) { vault.customer[name].push(items) }); } |
関数的なプログラミングが大好きな人には、その新しい配列を理解する(array comprehension)構文はとても身近に感じます。基本的に、配列の高次関数を使うより自然な書き方ができます。例えば、次のArray.prototype.map
の単純なイテレーションは:
1 2 | // ES 5 [1, 2, 3].map(function (i) { return i * i }); |
これはarray comprehensionを使って、書き直すとこんな風になります:
1 2 | // ES 6 [for (i of [1, 2, 3]) i * i]; |
いうまでもありませんが、Array.prototype.filter
を使うのと同じようにarray comprehensionはフィルタリングをサポートしています。次の2つの書き方の違いを較べてください:
1 2 3 4 5 | // ES 5 [1,4,2,3,-8].filter(function(i) { return i < 3 }); // ES 6 [for (i of [1,4,2,3,-8]) if (i < 3) i]; |
より良いのは、命令型のスタイルの入れ子ループのように、array comprehensionは複数のイテレーションを扱う事ができます。例えば、次の一行はチェスの位置を全て出力します(a1からh8まで):
1 2 | // ES 6 [for (x of 'abcdefgh'.split('')) for (y of '12345678'.split('')) (x+y)]; |
ランタイムのルネサンスやぁ
上に紹介されている言語の構文に対する段階的な改善の他に、ECMAScript 6はWeb開発者の作業をさらに楽にするランタイムの変更も提供しています。大規模アプリケーションの開発に直接関係する二つのものはclassとmoduleです。
class構文は理解しやすく簡潔であるようにデザインされました。次の例のコードはシンプルなクラスの生成を示しています:
1 2 3 4 5 6 7 8 9 10 | // ES 6 class Vehicle { constructor(color) { this.color = color; this.speed = 0; } drive() { this.speed = 40; } } |
constructor
という名前を持つクラスメソッドは特別な意味を持っています。これは(驚いたことに)クラスコンストラクタとして動作します。それ以外は全ては他のプログラミング言語と同じような、おなじみのクラスコンセプトと似ています。
このVehicle
クラスを今のECMAScript 5で書くと次のようになります:
1 2 3 4 5 6 7 8 9 | // ES 5 function Vehicle(color) { this.color = color; this.speed = 0; } Vehicle.prototype.drive = function() { this.speed = 40; } |
次に示すように、extends
キーワードを使って、ベースクラスから新しいクラスを派生できます。注:コンストラクタ内でのsuper
の使い方に注目して下さい。これは親クラスのコンストラクタを呼び出す方法を提供します。
1 2 3 4 5 6 7 8 | // ES 6 class Car extends Vehicle { constructor(brand, color) { super(color); this.brand = brand; this.wheels = 4; } } |
また、このサブクラス化はシンタックスシュガーです。手動でやったら、次のようなコードになるでしょう:
1 2 3 4 5 6 7 8 9 | // ES 5 function Car(brand, color) { Vehicle.call(this, color); this.brand = brand; this.wheels = 4; } Car.prototype = Object.create(Vehicle.prototype); Car.prototype.constructor = Car; |
modulesの取り扱いのビルトインサポートは非常に重要です。現代のWebアプリケーションはより複雑になってきていて、これまでアプリケーションの色々なパーツをモジュールにより構造化するにはとても手間がかかっていました。その手間を軽減する為にライブラリ、ローダー、などのツールがありますが、開発者はその非標準ソリューションを学ぶ為に時間を費やしていました。
モジュール構文はまだ検討中です。その可能性を感じるために、シンプルなモジュールを生成する次のコードをご覧下さい。
1 2 3 4 | const HALF = 0.5; export function sqrt(x) { return Math.exp(HALF * Math.log(x)); } |
このコードがmathlib.jsというファイル(またはモジュール)に入っているとすると、後で別な場所からsqrt関数を利用できます。
これはキーワードexport
が示しているようにエクスポートされているから可能となります。
上の例はエクスポートされた関数を表していますが、他にもエクスポートできる値がたくさんあります:変数、オブジェクト、クラス。
またHALFという定数はエクスポートされてないので、それはこのモジュール内だけで参照可能です。
では、どうやってmoduleを使うのでしょうか?
次の構文で、別のソースファイルからキーワードimport
を使って単純にこの関数をインポートできます:
1 2 | import { sqrt } from 'mathlib'; console.log(sqrt(16)); |
通常の場合には上記のもので十分ですが、ECMAScript 6のmodule対応はこれを超えます。例えばインラインモジュールの定義、エクスポートされた値のリネーム、デフォルトのエクスポートとインポートを宣言することが出来ます。その上、動的モジュール分析とローディングを容易にするローダーシステムもあります。
generator 関数もECMAScript 6の興味深い機能となります。これは関数の中でプログラム実行の中断と再開に対してとても役に立ちます。ジェネレーターの利用、特にシーケンシャル操作のためにはコールバックを利用する非同期の方法の代替となり、これが多勢の開発者を「コールバック地獄」に送りました。
ジェネレーター関数内に、yield
は実行を中断し、next
がそれを再開します。次のコードはシンプルなジェネレーターを示しています:
1 2 3 4 5 6 7 8 9 10 | function* foo() { yield 'foo'; yield 'bar'; yield 'baz'; } var x = foo(); console.log(x.next().value); // 'foo' console.log(x.next().value); // 'bar' console.log(x.next().value); // 'baz' |
Back from the Future
Firefoxや(実験中のフラグ付きの)Chromeは上で紹介したECMAScript 6機能のいくつかの対応を始めていますが、ECMAScript 6が完全に広く利用可能な状態になるのはまだまだ先です。しかし、良いことにこれに対して一つの可能な解決法があります。それはtranspiler
を使って、ECMAScript 6で書かれたコードを現在のブラウザが実行できるものに変換できます。
ECMAScript 6のポピュラーなtranspilerはTraceurで、これはGoogleのプロジェクトです(GitHub: github.com/google/traceur-compiler )。TraceurはECMAScript 6のたくさんの機能をサポートしますし、生成されたコードはモダンブラウザで非常にちゃんと動作します。
もう一つの選択肢はMicrosoftの TypeScript を採用する事です( http://www.typescriptlang.org/ http://www.typescriptlang.org/ )。 TypeScriptはECMAScriptと等価ではないかもしれません。 これはECMAScriptのスーパーセットとして考えることができます。 TypeScriptはECMAScriptにも含まれているポピュラーな機能をサポートしていますが、同時にオプショナルの静的タイピングを追加します。
TraceurとTypeScriptはオープンソースプロジェクトで多様な環境で利用できます。この二つのプロジェクトの他に、より特定な目的があるtranspilersもあります。例えば
es6-module-loader
es6-module-loader
(動的モジュールローディング)、
defs.js
defs.js
(let
とconst
のためだけ)、など色々あります。
最後に
構文の現代化、よりリッチなビルトインオブジェクト、さらにコンパイルしやすくなることで、ECMAScript 6は今後大規模なWebアプリケーションを作成する明るい未来を迎える道に光を照らしています。我々はECMAScript 6の方向性を見てわくわくしていますので、その変更を我々のフレームワークでどう利用できるか調査しています!