この記事は「ECMAScript2015/ES6特集」の第1回目です。この特集ではJavaScriptの次世代仕様であるECMAScript 2015(ECMAScript 6)を取り上げ、歴史や経緯から追加された機能や文法の詳細など複数回に渡って解説していきます。
ECMAScriptとJavaScript
そもそもECMAScriptとはなんでしょうか?JavaScriptとは一体何が違うのでしょうか?ECMAScriptとJavaScriptの関係は、JavaScriptが生まれた1995年まで遡ります。
JavaScriptは1995年、当時Netscape CommunicationsにいたBrendan Eich氏がWebで実行できるスクリプト言語として開発しました。その後Internet Explorerに搭載されWebの普及と共に浸透していきますが、当初はブラウザベンダーによる独自の拡張が多く互換性は低いものでした。そこで、Ecmaインターナショナルが中心となり、JavaScriptの中核仕様を抜き出して標準化したのがECMAScriptです。
つまり、ECMAScriptはJavaScriptの言語仕様であり、JavaScriptはECMAScriptの仕様に基づいた言語のひとつということになります。他のECMAScriptを実装している言語としては、ActionScriptやJScriptなどがあります。
ECMAScript 2015とECMAScript 6
この連載で解説していくのは、先日策定されたECMAScript(ECMA-262)の6th editionです。ECMAScript 6th editionの6を取ってECMAScript 6(ES6)と呼ばれていますが、正確な呼称をECMAScript 2015(ES2015)とし、今後は年単位のより細かいリリースを計画しているようです。
以降、本記事ではES2015と統一して表記しますが、リンク先がES6になっていたりすることもあります。
ES2015に追加される仕様
ES2015には、非力であったこれまでのJavaScriptに対して文法や機能が多く追加されており、より安全で便利なプログラム言語に進化しています。ES2015の仕様は公式ホームページに掲載されていますのでチェックしてみましょう。
ES2015から追加された機能を確認するには次のリソースが参考になります。特に後者は、ES6の機能とそれをES5以前で実装した場合にどうなるかのサンプルが比較されており、とても参考になります。
今回はこの中から主な新機能を、シンタックスとオブジェクトの2つに分けて紹介していきますので、「ES5でこのように書いていたものが、ES2015ならこう書ける!」というのを感じてもらえればと思います。
ES2015の新たなシンタックス
ES2015には、変数や関数の宣言・変数の代入・引数の展開・ジェネレータ関数など、実に多くの新しい文法が追加されています。
let・constキーワードによる変数宣言classキーワードによるクラス宣言- 関数の引数のデフォルトパラメータ(Default Parameters)
- 関数の可変長引数(Rest Parameters)
- アロー関数(Arrow Functions)
- ジェネレータ関数(Generator Functions)
- 配列展開(Array Spread)
- 分割代入(Destructing Assignment)
- 文字列のテンプレートリテラル(Template Strings)
importとexportによるモジュール構文(Module)
letとconstによる変数宣言
ES5以前のJavaScriptには関数スコープしか存在せず、馴染みの深いvarによる宣言はしばし事故を招き、関数スコープを利用するために、無名関数を使ったスコープ作成の記述を用いることがしばしばありました。これに対し、letやconstによる変数宣言ではブラケット{}によるブロックスコープが有効になります。
let foo = [1, 2, 3];
{
let foo = [4, 5, 6];
console.log(foo);
// => 4, 5, 6
}
console.log(foo);
// => 1, 2, 3
constは定数を宣言したいときに使うキーワードであり、宣言された変数には、宣言時を除いて値の代入が不可能であるという性質を持ちます。varによって宣言した変数は命名などで工夫していても常に値が書き換わるリスクを孕んでいます。しかしconstで宣言された定数に値を再代入しようとすると、例外が発生しプログラムの実行はストップし、値の不変性がプログラムレベルで保証されます。
{
const PI = 3.14;
const circleArea = function (radius) {
return radius * radius * PI;
};
console.log(circleArea(3));
// => 28.26
// PI = 3.1415;のような再代入はできない
}
console.log(PI);
// => undefined
classによるクラス構文
JavaScriptでnewを伴うオブジェクトの作成をするには、以下のように関数を宣言することで「クラスのようなもの」を実現してきました。このES5を使ったクラス表現はnewを使わずとも関数として呼び出すことが可能であるため、グローバル変数を書き換えてしまうリスクを常にはらんでいます。これをES2015のclassを使って書き直してみます。
// これまでのクラスのようなもの
function Human(name) {
this.name = name;
}
Human.prototype.hello = function () {
console.log('My name is ' + this.name);
};
// ES2015のclassを使って書き直す
class Human {
constructor(name) {
this.name = name;
}
hello() {
console.log('My name is ' + this.name);
}
}
ES2015のclassを使って宣言されたHumanはnewキーワードなしで使うことはできないので、より副作用がない形でクラスを宣言可能になっています。
アロー関数による関数宣言
functionを使った関数の定義に加えて、ES2015からはアロー関数が使えるようになります。functionというキーワードを使わずに=>を使って宣言することが可能になります。さらに、関数本体が単一式である場合はブラケット{}とreturnも省略できます。
// 従来のfunctionを使った書き方
let plus = function(x, y) {
return x + y;
};
// アロー関数でfunctionを省く
let plus = (x, y) => {
return x + y;
};
// 単一式の場合はブラケットやreturnを省略できる
let plus = (x, y) => x + y;
functionが省略可能になることでよりシンプルに書けますが、アロー関数は宣言しているスコープのthisを引き継ぐという特徴があります。もちろんこの機能も魅力のひとつですが、気にせずに使っていると思わぬミスをしてしまうかもしれません。
// ブラウザのグローバル空間に於いて以下を実行すると
// グローバルのthisであるwindowがアロー関数内のthisになる
window.setTimeout(e => {
console.log(this === window); // => true
}, 1000);
ES2015に追加されるオブジェクト
シンタックス以外にも、様々な機能を備えたオブジェクトが追加されます。新たに追加されるオブジェクトの他にも、ArrayやObjectといった既存のオブジェクトに対して様々な関数が追加されており、機能が強化されています。
Promise: 非同期処理を抽象化するデザインパターンSymbol: ユニークな値を表現する新たなプリミティブ型Reflect/Proxy: オペレーションに処理を介入させる機能Set/WeakSet: 一意なデータスタックを表すオブジェクトMap/WeakMap: Key-Valueのデータ構造を表すオブジェクト
Promise
Promiseは既にお馴染みの人も多いでしょう。JavaScriptのコールバック地獄に対する解決策のひとつとしてJavaScriptの世界に持ち込まれたPromiseは、需要も高くライブラリ実装も多かったですがES2015からはついにネイティブに組み込まれます。
// ES5
asyncFunc1(function () {
asyncFunc2(function () {
asyncFunc3(function () {
// asyncFunc1 → asyncFunc2 → asyncFunc3
// という実行順をコールバックの入れ子で実現する
});
});
});
// Promiseを使ってコールバックの入れ子を避ける
// asyncFuncはそれぞれがPromiseオブジェクトを返却している
asyncFunc1().then(function () {
return asyncFunc2();
}).then(function () {
return asyncFunc3();
});
最近ではService WorkerやFetchなどの様々なブラウザAPIがPromiseベースで設計されており、今後もPromiseは多く使われていくでしょう。今のうちにマスターしておきたいところです。
Symbol
Symbolはユニークな値を表現するプリミティブな新しい型です。Symbol型の値はStringのようにプロパティのキーとして使うことができますが、文字列とは異なりSymbolのインスタンスを使わないと参照することができません。
const key1 = Symbol('foo');
let object = {};
object[key1] = 'Value for key1';
console.log(object['foo']); // => undefined
console.log(object[key1]); // => Value for key1
ProxyとReflect
Proxyはオブジェクトへのアクセス時に処理を割り込ませる機能を提供するオブジェクトです。Proxyを使うと、値の参照時(get)や代入時(set)、プロパティの削除時などのタイミングで処理を介入させることが可能です。
let person = {
name: '1000ch'
};
let proxied = new Proxy(person, {
get: (target, name, receiver) => ${name} is ${target[name]},
set: (target, name, value, receiver) => {
console.log(name + ' is changed.');
Reflect.set(target, name, value, receiver);
}
});
console.log(proxied.name); // => name is 1000ch
Proxyを使ってインターセプトすると、デフォルトの振る舞いを邪魔することになります。そこで、デフォルトの挙動を再現するためにReflectというオブジェクトがあります。Reflectにはインターセプトできるハンドラ(ここでいうgetやset)に対応したメソッドが用意されているので、それを呼び出してやることでデフォルトの挙動を再現することができます。
MapとSet、WeakMapとWeakSet
SetとMapは、これまで配列やオブジェクトを使って表現していたデータスタックやKey-Valueといったデータ構造化をサポートします。Setのインスタンスには同一の値を追加できず、ユニークな値がスタックされていることが保証されます。MapはObjectのようにキーとそれに対する値を保持しますが、Objectと異なるのはキーに文字列以外のオブジェクトを指定できる点です。これによって様々なオブジェクトに対して値を関連付けることができます。
let map = new Map(); let set = new Set();let object = {}; let array = [];
set.add(object); set.add(array); set.add(window); set.add(window); // => 追加されない set.has(window); // => true set.delete(window); set.has(window); // => false
map.set(object, 100); map.set(array, 'Value for Array'); map.set(window, {}); map.get(array); // => Value for Array map.has(object); // => true map.delete(window); map.get(window); // => undefined
WeakSetとWeakMapはその名の通り、弱いSetとMapです。SetとMapに比べて参照が弱くなっており、値への参照が存在しなくなるとエントリが自動で削除されるという特徴があります。例えばDOMのオブジェクトに対してWeakMapを使って値を関連付けます。そこでキーに指定したDOMオブジェクトを削除すると、関連付けた値を参照する手段はなくなるため関連付けたKey-Valueのエントリは自動で削除されるのです。Objectで表現していた場合はどこからも参照されないエントリが残ることになるのでメモリリークの要因になりますが、WeakMapを使うことでそのリスクを減らしていけるでしょう。
まとめ
第一回ではECMAScriptとJavaScriptの歴史、ES2015に追加される機能の概要、主な機能の紹介をしました。次回は各ブラウザの実装状況や、実践導入に向けて抑えておくべき知識などを解説していきます。