21. Harmony から ECMAScript 2015 まで
ES42 の中止により、TC39 は将来の JavaScript が歩む進化的な道を比較的先入観を持たずに計画する機会を 1999 年以来初めて手にした。もはや TC39 はより良い言語を作るために最初から設計をやり直そうとは考えていなかった。TC39 は成功への道を歩み始めており、取り組みが実を結ぶまでにかかった時間はわずか 7 年だった。
21.1 Harmony の始まり
TC39 の Harmony プロジェクトは二度の ES4 の取り組みで決定された事項に縛られなかったものの、利用できる部分は利用した。また TC39 は ES5 プロジェクトの中で決定された事項には縛られたものの、ES5 が向かった方向は一般に Harmony が向かうと期待された方向と同じだった。実際 2008 年の後半および 2009 年のほとんどにおいて、TC39 会合の時間の多くは ES5 を完了させる作業に使われた。このため TC39 全体が安心感を持って ES5 仕様をベースラインとした Harmony の取り組みを始めることができた。
21.1.1 ストローマンと目標
2008 年 8 月、ECMAScript wiki に「Harmony ストローマン1」というページが作られ、メーリングリストの es4-discuss は es-discuss語 に改名された。Harmony が発表されると、es-discuss では Harmony で追加される可能性のある機能に関する新たな議論が大量に始まった。これを受けて生まれたワークフローは次のようなものである。まず新しいアイデアが es-discuss あるいは TC39 会合で生まれる。そのアイデアを TC39 メンバーが役に立ちそうだと思った場合、初期設計あるいは機能の説明をまとめた完全な文書を執筆し、それを wiki の「Harmony ストローマン」ページに投稿する。TC39 での反応に応じて、アイデアは放棄されるかアイデアを洗練させるために同じプロセスが反復される。2008 年 11 月時点で wiki のストローマンページ [TC39 Harmony 2008] には次の項目が存在した:
- クラス
- 名前
const
- ラベルへの
return
- ラムダ
- 型
- 字句的スコープ
「クラス」を除く全ての項目は Dave Herman が作成した短いストローマン提案へのリンクである。「クラス」はプレースホルダーだった。
Harmony で追加される可能性のある機能の議論は拡大し、TC39 は 2009 年の夏に活動をさらに構造的にすることを決定した。2009 年 7 月の会合 [TC39 2009a] で TC39 メンバーはそろそろ Harmony の目標を定めるべきだと判断し、いくつか追加や修正を加えれば ES3.1 の目標 [Crockford 2008a] は Harmony でも利用できると結論付けた。Brendan Eich [2009a] は更新されたバージョンの目標を Harmony の目標声明として wiki に投稿した (図 38)。
要件
- 新しい機能には具体的なデモを要求する。
- JavaScript をカジュアルな開発者にとって快適なように保つ。
- 「小さく始めて、何度もプロトタイプを作る」という JavaScript の特徴を保持する。
目標
- 次のコードを書くためにより良い言語となる:
- 複雑なアプリケーション
- そういったアプリケーションが共有する (おそらくは DOM を含む) ライブラリ
- 新しい版をターゲットとするコードジェネレータ
- テスト可能な仕様 (理想的には大部分が ES5 内でホストされる定義的インタープリタ) に転換する。
- 相互運用性を向上させる。可能な部分ではデファクトスタンダードを採用する。
- バージョン番号を可能な限り単純かつ線形に保つ。
- 静的に検証可能なオブジェクト機能モデルのセキュアな部分集合をサポートする。
手段
- ES5 以降で必要となる意味論的状態の追加を最小化する。
- 次の要素に対する構文的利便性を提供する:
- 優れた抽象パターン
- 高整合性パターン
- これらを中心的な意味論への脱糖によって定義する。
- 分かりにくい構文や問題のある構文を (オプトインのバージョン選択あるいはプラグマを通して) 削除する。
- ES5 の strict モードで Harmony ビルドを作成することを検討する。
- 仮想化機能をサポートし、ホストオブジェクトのエミュレートを可能にする。
21.1.2 チャンピオンモデル
Dave Herman は仕様の策定で「チャンピオンモデル (champions model)」を採用すべきだと TC39 に提案した2。チャンピオンモデルを使うとき、個々の機能は一人のメンバーあるいは少人数のメンバーからなるグループ (「チャンピオン3」) が担当する。チャンピオンは初期のストローマン提案を執筆し、実際の仕様に統合される準備が整うまで改善を続ける。初期ストローマンから始まって提案が進化する中でチャンピオンは TC39 全体にプレゼンテーションを行い、TC39 や他のレビュアーからのフィードバックを受ける。フィードバックをどう受け止めるか、特にフィードバックに基づいて提案を変更するかどうかはチャンピオンに任せられる。このモデルではチャンピオンプレゼンテーションの間に TC39 が直接設計を行うのは避けるべきとされる。ただ仕様の最終提案に含めるかどうかの最終的な判断では TC39 全体のコンセンサスが必要になる。
TC39 は Herman によるチャンピオンモデルの提案を受け入れ、事実上広く利用した。ただし、このモデルが上手く行かなかったこともあった。この時期メンバーの中心的グループは比較的小さく、非常に高い技術力を持っていた。彼らは「委員会による設計」を少しだけ行う誘惑に打ち勝つことができなかったり、あるいは実際に提案を前に進める最も効率的な方法が「委員会による設計」であったりした。特定の機能や設計の問題に対して複数のチャンピオンが異なるアプローチと提案を示すこともあり、そのときは TC39 が対立する提案の中から一つを選択するか全てを破棄するかを選ぶ必要があった。
21.1.3 機能セットの選択
2009 年と 2010 年のほとんど、そして 2011 年の前半にわたって、TC39 では様々なチャンピオンがストローマン提案を作成し、TC39 による入念な考査を受け、提案の状態を「受理」にするために必要なコンセンサスを得ようと努力した。2009 年の 8 月時点で wiki のストローマンページ [TC39 Harmony 2009] にある提案は最初の 7 個から 21 個にまで増加した。Berndan Eich [2010a] は提案をテーマごとにまとめ (図 39)、Harmony の目標を提示したページに追加した。2010 年 12 月にはストローマンページ [TC39 Harmony 2010b] の提案が 66 個まで増加し、同じページには破棄あるいは延期された 17 個の提案も掲載されていた。2011 年 5 月初頭のストローマンページ [TC39 Harmony 2011c; 補遺 N] には 100 個以上のエントリーがあり、承認された提案のページ [TC39 Harmony 2011a] には 17 個のエントリーがある。
テーマ
- モジュール関係の機能、つまりソースコードの一単位の輪郭を記述することでその内部を外部のユーザーに隠す手段
- 作用の伝播を妨げる、あるいは作用の伝播が特定の参照を通してのみ行われるようにするための隔離機構
- ゼロ権威のメーカースタイルモジュール
原始オブジェクト (primordial object)/コンテキストオブジェクト/組み込みオブジェクトとモジュールのその他の連携
- ブラウザにおける隔離の欠如: 接続された複数のグローバルオブジェクト
- 仮想化: 階層的なゲストコードのホスト、接点を持たないオブジェクトシステム間の橋渡し、特に重要なのがホストオブジェクトのエミュレート
- プロキシ
- 弱参照 (weak reference) あるいはエフェメロン (ephemeron)
- 制御作用 (control effect): 反復と状態機械のコードをより簡単にするため
- 限定継続 (delimited continuation)
- ジェネレータとイテレータ
- TC39 がライブラリの進化を妨げないためのライブラリ/ツール支援
Object.hashcode
- 何らかのバイト配列
- 値型 (十進算術などのため)
- 言語の変革: ユーザーを悪い形式のコードから遠ざけるために必要な「より良いニンジン」
- ブロックスコープの
let
,const
,function
- デフォルトパラメータと残余パラメータ
- 分割代入
- バージョン付け: Harmony に新しい構文が含まれるため
- このテーマはオプトインのバージョン選択処理の最小化、移行の簡略化、以降の版を見越した将来性の確保に関するもの
2009 年、Brendan Eich は「ES.next」の機能を 2011 年 5 月に凍結させ、2012 年 6 月の Ecma 総会での承認を受けるという TC39 としての目標を提案する [TC39 2009b]。2011 年 5 月が近づくにつれ 2012 年 6 月の機能凍結は不可能であることが明白になったものの、仕様策定のために作業が進んでいた機能リストをドラフトにまとめることになった。2011 年 5 月の会合 [TC39 2011b] のほとんどはストローマンのリストを選別 (トリアージ) し、残っているストローマン提案のどれを「Harmony 提案」の状態に進めるかについてコンセンサスを得ることに費やされた。進めるか破棄するかが少しのレビューで決まった提案もあれば、重要な機能の提案の中には当時のストローマンに TC39 が満足していなかったにもかかわらず進めることになったものもあった。そういった提案はプレースホルダーであり、改善された提案の策定が進行中とされた。モジュールとクラスの二つはそういった扱いを受けた。Harmony の最終的な機能セットはこの会合では凍結されず、ES.next の策定が進む中でいくつかの提案が追加されたり破棄されたりした。ただ、この会合で作成された提案リストは最終的に ES2015 となる仕様の輪郭を形作った。2011 年 5 月の会合の出席者を図 40 に、会合後に公開された Harmony 提案のページ [TC39 Harmony 2011b] を補遺 O に示す。
Avner Aharon | Microsoft | Waldemar Horwat | ||
Douglas Crockford | Yahoo! (Phone) | Mark Miller | ||
Brendan Eich | Mozilla | John Neumann | Ecma | |
Cormac Flanagan | UCSC | Alex Russell | ||
David Fugate | Microsoft | Mike Samuel | ||
Dave Herman | Mozilla | István Sebestyén | Ecma | |
Luke Hoban | Microsoft | Sam Tobin-Hochstadt | Northeastern Univ | |
Bill Frants | Periwinkle (guest) | Allen Wirfs-Brock | Mozilla |
21.1.4 執筆の開始
TC39 のチャンピオンがまとめた Harmony 提案を使って実際の ES.next の仕様文書を作成する全体的な責任はプロジェクト編集者の Allen Wirfs-Brock にあった。彼は Microsoft で TC39 関連の仕事の他にもプロジェクトを担当していたが、ES Harmony に集中するため 2010 年 12 月に Microsoft を離れて Mozilla に加わった。
ES4 と ES5 の経験から、Wirfs-Brock は実際の仕様文書に継続して取り組むことが規格の新しい版の完成に向けた進捗を生むために不可欠であると学んでいた。2011 年 6 月 22 日、彼は確固たる意志を持って最近完成した ES5.1 仕様のソースファイルを開き、表紙のタイトルを「第 6 版ドラフト」に変更し、それを ES6 仕様のドラフトのベースラインとして保存した。それから彼は 5 月の機能トリアージおよび過去二年間で TC39 が下した決断に基づいて新しい文書をそのドラフトに書き加える作業をすぐに開始した。7 月 12 日には「ES.next 仕様の第 1 作業ドラフト」が投稿される [Wirfs-Brock et al. 2011a, b]。このドラフトに含まれる変更の概要を図 41 に示す。これは 38 回にわたって公開されるドラフトの中で最初のものであり、最後のドラフトは 2015 年 4 月 14 日に wiki に投稿された [Wirfs-Brock et al. 2015a, c]。
5.1.4 | 補助文法の概念を導入 |
5.3 | 静的意味論規則の概念を導入 |
8.6.2 | [8.6.2] および様々な箇所。内部プロパティ [[Class]] を削除。代わりに様々な内部トレードマークプロパティを追加。 |
10.1.2 | 新しい ES.next 構文を利用できるコードを意味する「拡張コード」の概念を導入。加えて「strict モード」を ES5 の strict モードおよび拡張コードを意味する用語として再定義。 |
11.1.4 | 配列リテラルでスプレッド演算子を使うための構文と意味論を追加。 |
11.1.5 | プロパティ値の省略記法の構文と意味論、およびヘルパーの意味論的抽象操作をいくつか追加。 |
11.2, 11.2.4 | 引数リストにおけるスプレッド演算子の構文と意味論を追加。 |
11.13 | 分割代入演算子の構文と意味論を追加。 |
12.2 | 宣言および仮パラメータリストで分割代入をサポートするための Binding Pattern 構文および部分意味論を追加。 |
13 | 残余パラメータ、パラメータのデフォルト値、仮パラメータリストにおける分割代入パターンをサポートするための構文を追加。加えてこれらの機能に対する静的意味論を追加。ただしパラメータのインスタンス化については未完了。これらの機能が増したパラメータリストにおける引数リストの「長さ」を定義。 |
15 | 第 15 節にある関数の仕様が事実上内部メソッド [[Call]] の定義であることを明確化。 |
15.2.4.2 | [[Class]] を使わない形で toString を規定し直した。明示的な拡張機構の追加は依然 to-do であることに注意。 |
Annex B | 「ウェブブラウザの ES 実装における省略可能な規定的機能」に改題。 |
21.1.5 One JavaScript
Harmony の活動が始まったときから、Harmony で導入される新しい機能の一部あるいは全てを使うには何らかの明示的なオプトインが必要になるだろうと TC39 は考えていた。これは既存の JavaScript プログラムが無効になる破壊的変更を含む提案がいくつもあった ES4 の時代から受け継いだ考え方である。Harmony は破壊的変更の採用に ES4 より保守的だったものの、いくつかの破壊的変更は真剣に検討されていた。Harmony 策定の最初の 3 年で正確なオプトインメカニズムは決定しなかったが、その選択肢は頻繁に議論された。ES6 の第 1 ドラフトには ES5 の strict モードの上位集合としての「拡張コード (extended code)」という概念が含まれるものの、オプトインのメカニズムはまだ説明されていない。検討された選択肢には HTML の <script>
要素の属性を使った外部からのオプトイン、use mode
という新しいプラグマ文、構文的な分離、そして use strict;
に似た新しいディレクティブの追加があった。オプトインに関して、いったい将来いくつのモードが必要になるのかという懸念が TC39 内に存在した。規格の版が新しくなるたびに新しいオプトインモードが必要になるのか? それは言語のユーザーと実装者の両方にとって複雑性の観点から大きな負担になると思われた。
Dave Herman [2011b] は「ES6 にオプトインは必要ない」というタイトルのメッセージを es-discuss に投稿し、ES6 で行われる破壊的変更は最小限にした上で ES6 モジュールとしてカプセル化されたコードでのみ効果を持つようにするべきだと主張した。新しい機能の大部分を非破壊的変更にして、任意のコードがモジュール内かどうかに関わらず同一の振る舞いをするのが望ましい、とも彼は述べた。この意見に従うと一部のケースで機能の再設計が必要となり、検討中の機能が破棄される可能性が生じるケースもあった。これらのアイデアは Herman のメッセージに付いた 150 個以上の返答の中で洗練された。次回の TC39 会合で Herman [Herman 2012] は「One JavaScript」と題されたプレゼンテーションを行い、この洗練されたアイデアを示した。ECMAScript Harmony を利用する将来のプログラマと実装者が統合された単一の JavaScript という言語を思い描けるようにするべきで、モードやバージョン、方言といった要素を考えなければならないのは望ましくないというのが要点だった。この観点に沿って ES.next を設計するのが TC39 の役目である、とも主張された。その会合の多くはこの提案と Harmony の様々な機能に対するその影響についての議論に費やされた。最終的に Harmony において「1JS」の取り組みを行うことに合意があった。次回の仕様ドラフト [Wirfs-Brock et al. 2012a] では拡張コードの概念が姿を消し、破壊的となる変更を削除するための様々な変更が加えられた。
21.1.6 Brendan の夢
2011 年 1 月、2 年以上にわたって Harmony に取り組んできた Brendan Eich [2011b] は「私の夢の Harmony」というタイトルのブログ記事を公開し、言語の進化と標準化委員会に関する意見を語った。記事の中心的な話題は Harmony JavaScript の将来に彼が期待することの例示だった:
...私は JavaScript Harmony をリフレッシュしたバージョンを提示する。この印象派的運動はもちろん (今のところ) 正式なものではないが、無名の人物による気色悪い二次創作でもない。このようなことは実際に起こり得る。あなたの助けがあれば可能性は増すだろうし、もっと上手くできるだろう (その方法は最後にさらに述べる)。
私は Ecma TC39 の現在のコンセンサスである Harmony、TC39 の一部が支持している Harmony に向けたストロー[マン]提案、そして自分のアイデアの境界線を曖昧にしている。これは意図的である。JS には何らかの新たな概念的整合性が必要だと私は考えているからだ。委員会による無難な設計は JS に必要ない。「全ての提案を合わせよう」的なアプローチ (TC39 では上手く行かないだろう) も、盲目的な「提案の共通部分を取ろう。空集合になったって構わない」的なアプローチ (これも上手く行かないだろうし、こちらの方が悪い結果となると思われる) も必要ない。
彼は様々なユースケースが ES5 の機能でどのように書けるかを示し、それから彼が夢見る Harmony では同じ処理がどのように書けるかを示した。後者は中間段階にあった Harmony 提案の概観を示しており、それらがどのように実際の ES2015 の機能へ進化したかを見ることができる。彼が示した機能の一部は ES2015 に含まれておらず、多くは最終的に何らかの形で姿を変えた。既存の機能の構文と意味論を変えるオプトインを通じた破壊的変更が 1JS のアプローチにより不可能になったために提案を変える必要が生じたケースもある。
この機能進化の全体像を捉えるために、これから 2011 年に Brendan Eich が示した「夢4」と後に ES2015 で現実となった機能をいくつか比較する。
let block_scoped = "yay!"
const REALLY = "srsly"
function later(f, t, type) {
setTimeout(f, t, typo) // 事前エラー
}
ES2015 の現実: ブロックスコープの let
宣言と const
宣言は存在するものの、自由な変数参照に対する事前エラーは 1JS によって除外された。
return
の追加、自由変数を持たない関数に対する冗長なクロージャの削除:
const #add(a, b) { a + b }
#(x) { x * x }
ES2015 の現実: #
記法ではなくアロー関数が追加された。自動的な値の return
は本体が式のアロー関数でのみ起こる。またオブジェクトリテラルとクラス本体におけるメソッドの簡略記法が追加された。観測できないクロージャ最適化は実装に任される:
const add = (a, b) => a + b // 本体が式なので暗黙な return を持つ
x => x * x
x => { console.log(x); return x * x } // 本体が文なので明示的な return が必要
// オブジェクトリテラルとクラス宣言におけるメソッド定義
class {
// ここでは本体を式にはできない
add(a, b) { return a + b }
}
this
シャープ関数における this
はそれを囲むコードにおける this
と同じ束縛となる:
function writeNodes() {
this.nodes.forEach(#(node) {
this.write(node)
})
}
ES2015 の現実: this
およびその他の関数スコープの暗黙の束縛はアロー関数において字句的に束縛される:
function writeNodes() {
this.nodes.forEach(node => this.write(node))
}
const point = #{x: 10, y: 20}
point === #{x: 10, y: 20} // true
ES2015 の現実: 含まれていない。Harmony で完全に策定されなかった拡張可能な値型という概念と結び付きすぎていた。
function printf(format, ...args) {
/* args を実際の配列のように使う */
}
function construct(f, a) {
return new f(...a)
}
let [first, second] = sequence
const {name, address, ...misc} = person
ES2015 の現実: ...
演算子を使ったオブジェクトの分割代入がサポートされないことを除けばそのまま追加された。オブジェクトの分割代入は以降の版で追加された。
module M {
module N = "http://N.com/N.js"
export const K = N.K // 値 N.K がエクスポートされる
export #add(x, y) { x + y }
}
ES2015 の現実: 一つのファイルに一つのモジュールであり、モジュールを定義する明示的な区切りは存在しない。import
と export
はさらに細かく定式化された。値ではなく束縛がモジュール間で共有される:
// http://M.com/M.js の内容
export {K} from "http://N.com/N.js" // N.K という束縛がエクスポートされる
export const add = (x, y) => x + y
for-in
文:
module Iter = {"@std:Iteration"}
import Iter.{keys,values,items,range}
for k in keys(o) { append(o[k]) }
for v in values(o) { append(v) }
for [k,v] in items(o) { append(k, v) }
for x in o { append(x) }
#sqgen(n) { for i in range(n) yield i*i }
return [i * i for i in range(n)] // 配列の内包表記
return (i * i for i in range(n)) // ジェネレータの内包表記
ES2015 の現実: 1JS はモジュールとプロキシに依存した for-in
のオーバーロードではなく for-of
文を動機付けた。標準のキー/バリュー/エントリーのプロトコルが組み込みのコレクションクラスに対して定義された。内包表記は将来性の懸念があり Harmony からは除外された:
for (k of o.keys()) append(o[k])
for (v of o.values()) append(v)
for ([k,v] of o.entries()) append(k, v)
for (x of o) append(x) // o は自身のデフォルトイテレータを提供する
function *sqgen(n) {for (let i of Array(n).keys) yield i*i } // ジェネレータ
if x > y { alert("paren-free") }
if x > z return "brace-free"
if x > y { f() } else if x > z { g() }
ES2015 の現実: 含まれていない。変化が大きすぎるとして TC39 によって破棄された。1JS は古い形式が認識され続けることを要求し、古い形式と新しい形式が混ざると設計およびユーザーに対して余計な複雑性が生じる。
21.2 仕様の作り直し
ECMAScript の意味論を実行可能かつ検証可能な仕様を使って記述したいという願望は ES42 の活動から受け継がれたものとして存在したものの、仕様言語として ML は却下されていた。Harmony の活動が始まったころ、Allen Wirfs-Brock [2009] は Harmony を規定する定義的インタープリタを ES5 JavaScript で書くというアイデアを考え付いた。このアイデアは Harmony の目標声明 (図 38) に含まれてさえいる。しかし 2010 年の 4 月まで定義的インタープリタに関して大きな進展はなく、このアプローチが成功することを TC39 メンバーは確信できなくなっていた。そんな中 ES5 における疑似コードの改善(補遺 Q) により以前の版が抱えていた利便性の問題はほとんどが解決され、Test262 の作業が進むにつれ包括的なテストスイートは仕様の実装だけではなく検証に対しても有用であることが示された。仕様の形式化が再度議論された 2010 年 5 月の TC39 会合 [TC39 2010] では、出席者の多くが現状維持を好ましく思っていることが明らかになった。Apple の Oliver Hunt は実装者として、ES5 の疑似コードは自身がこれまでに目にしたどんな実行可能な仕様より優れていると感じると語った。Harmony の策定では疑似コードの利用を続ける決定に合意があった。
プロジェクト編集者の Allen Wirfs-Brock にとって、仕様の作成は単なる統合の作業ではなかった。理論上はチャンピオンによって作成された提案は仕様へと簡単に統合できるようになっているはずなのだが、実際にそうであることはほとんどなかった。統合できるクオリティの疑似コードを書くための仕様の執筆手法や形式化手法に慣れていないチャンピオンもいれば、意味論の詳細な仕様を書くのに必要な専門知識を持っていないチャンピオンもいた。Wirfs-Brock は多くの提案を仕様に統合できるよう推敲し、意味論の詳細を詰め、提案を規定するアルゴリズムを書くか書き直すかしなければならなかった。
チャンピオンからの提案は焦点が狭く、自身が定義する機能にしか注意を向けない傾向があった。優れた提案は自身が提案する機能に加えて、言語が持つ既存の機能との相互作用も考慮する。しかし最も高いスキルを持つチャンピオンでさえ、自身の機能と他のチャンピオンが同時に作成していた他の提案に含まれる機能との相互作用を全て考慮するのに苦労していた。編集者は提案を実際の仕様に取り込む前に確認しなればいけなかったので、既存の言語と全ての Harmony 提案を統合して出来上がる ES6 の全体像を最もよく理解していたのは Wirfs-Brock だった。彼は複数の提案に影響する機能横断的な懸案事項、そして提案に構文的および意味論的な一貫性を持たせることに特に注意を払った。彼は承認された提案を統合するとき、提案を合成可能な直交する機能の集合 [Lindsey 1993] に変形しよう試みた。このために提案の構文的あるいは意味論的な詳細に変更が必要になることもあれば、重要な機能を削除する必要が生じることさえあった。こういった変更はその後チャンピオンに提示されなければならず、たいていは TC39 全体の承認を必要とした。
21.2.1 仕様の再構成
1997 年の第 1 版 (図 9) から ES5.1 まで、ECMAScript 仕様の全体的な構成は同じままだった。Allen Wirfs-Brock は ES5 仕様に取り組む中で、仕様に含まれる文書の基礎的な順序が分かりにくいことに気が付いた。彼はその後 ES5 仕様が三つの根本的に異なる部分からなることを理解した:
- ECMAScript 仮想機械およびその実行時要素と意味論
- ECMAScript 言語の構文と意味論、そして言語と仮想機械の対応付け
- 全ての ECMAScript プログラムから利用可能な標準ライブラリオブジェクト
これまでの ECMAScript 仕様は三つの部分を区別せずに提示しており、基礎的な構造が分かりにくくなっていた。Allen Wirfs-Brock は仕様を明示的に三部構成にすれば理解しやすくなり、ES6 で新しく加わる大量の文書も分かりやすく提示できるだろうと考えた。TC39 はこのアイデアに合意した。図 42 に ES2015 仕様と ES5 仕様の構成の比較を示す。
節 | ECMA-262, 第 5 版 (245 ページ) | ECMA-262, 第 6 版 (545 ページ) |
---|---|---|
1 | 範囲 | 範囲 |
2 | 準拠 | 準拠 |
3 | 規定的参考文献 | 規定的参考文献 |
4 | 概要 | 概要 |
5 | 規則 | 表記規則 |
6 | ソーステキスト | ECMAScript のデータ型と値 |
7 | 字句規則 | 抽象操作 |
8 | 型 | 実行可能コードと実行コンテキスト |
9 | 型変換とテスト | 通常オブジェクトと奇異オブジェクトの振る舞い |
10 | 実行可能コードと実行コンテキスト | ECMAScript 言語: ソースコード |
11 | 式 | ECMAScript 言語: 字句文法 |
12 | 文 | ECMAScript 言語: 式 |
13 | 関数定義 | ECMAScript 言語: 文と宣言 |
14 | プログラム | ECMAScript 言語: 関数とクラス |
15 | 標準組み込み ECMAScript オブジェクト | ECMAScript 言語: スクリプトとモジュール |
16 | エラー | エラー処理と言語拡張 |
17 | ECMAScript 標準組み込みオブジェクト | |
18 | グローバルオブジェクト | |
19 | 基礎オブジェクト | |
20 | 数値と日時 | |
21 | テキスト処理 | |
22 | 添え字付きコレクション | |
23 | キー付きコレクション | |
24 | 構造化データ | |
25 | 制御抽象化オブジェクト | |
26 | リフレクション |
21.2.2 新しい用語
ES6 は仕様で使われる用語の一部を明確化あるいは更新する機会を提供した。注意を必要とした領域の一つがオブジェクトに関する用語である。JavaScript 1.0 実装は JavaScript プログラムにホスト固有のオブジェクトへのアクセスを与えており、ホスト固有のオブジェクトの意味論には ECMAScript コードを使って作られるオブジェクトの意味論と意外な形で異なる点が多くあった。ES1 仕様は実装様式の異なるオブジェクトを指して「オブジェクト」「ネイティブオブジェクト」「標準オブジェクト」「組み込みオブジェクト」「標準ネイティブオブジェクト」「組み込みネイティブオブジェクト」「ホストオブジェクト」といった言葉を使っていた。こういった名称の区別はあまり有用ではなく、正確にどのカテゴリが通常のオブジェクトと異なる意味論を持つことができるのか、そして JavaScript プログラマが作成したオブジェクトはいずれかのカテゴリに属するのかどうかが明確ではかった。
ES6 の目標は標準ライブラリとホストオブジェクトの大部分でセルフホスト実装を可能にすることだった。もしセルフホストが可能になれば、ホスト提供のオブジェクト、エンジン提供のオブジェクト、そしてプログラム提供のオブジェクトの間の差異はあまり重要でなくなり、オブジェクト間の意味論的な違いがオブジェクトの提供元や実装に使われたテクノロジよりも重要となる。
こうして、通常の意味論を持つオブジェクトと通常とは異なる (異常な) 意味論を持つオブジェクトを区別する基礎的な用語が必要になった。Douglas Crockford [TC39 2012b] は Ecma における最上位の会員カテゴリを指す用語を借りて、JavaScript でオブジェクトリテラルあるいは new Object()
で作られるオブジェクトと同じ意味論を持つオブジェクトを指す「通常オブジェクト語 (ordinary object)」という用語を提案した。彼は通常オブジェクトと異なる意味論を持つオブジェクトを「奇異オブジェクト語 (exotic object)」と命名した。通常オブジェクトと奇異オブジェクトはどちらもホスト、エンジン、アプリケーションプログラマから提供でき、JavaScript あるいは他の言語を使って実装できる。
21.2.3 新しい種類の意味論
ES6 より前の版において、疑似コードで表されたアルゴリズムの大部分は、標準ライブラリ関数を定義するものを除けば、文法の生成規則と関連付けられ、その生成規則の実行時における評価意味論を規定した。そういったアルゴリズムが規定するのは対応する生成規則に関連付く唯一の意味論なので、アルゴリズムを命名する必要はなかった。一方で型の変換やオブジェクトの意味論を定義する内部メソッドのように文法と直接は関連付かないアルゴリズムもいくつかあり、それらは生成規則の評価アルゴリズムから参照できるように名前が付いていた。
ES6 で導入された新機能には、オブジェクトの分割代入のように、振る舞いが複雑なために仕様が様々な生成規則を参照しなければならないものが存在した。情報の収集あるいは複数のパースノードにまたがる評価ステップの進行のためにパースツリーを何度も走査するアルゴリズムもあった。また一貫性を持たせるために、文法と結び付いた共通の振る舞いが複数の機能から利用されることもあった。こういった要件を満たすために、ES6 仕様では暗黙に命名された評価アルゴリズムだけではなく名前付きのアルゴリズムをパースノードに関連付けられるようになった。そういったアルゴリズムは名前で参照され、名前の解決には文法記号も使われる。通常そういった名前付きアルゴリズムは同じ名前のアルゴリズムを複数の生成規則に対して定義できるという意味で多相的であり、実際に使われるアルゴリズムの選択は現在のソーステキストのパースで使われた文法記号の導出に依存する。
実装間の差異を最小化するという目標があったので、ECMA-262 は版を重ねるごとにエラーの条件やその条件を検出する処理の定義がより明確になっていった。ES3 は「事前エラー (early error)」という概念を暗黙に導入しており、この概念は ES5 でさらに洗練された。事前エラーとはスクリプトに含まれるエラーであって、スクリプトの評価に先立って検出、報告されるものを言う。事前エラーが検出されるとスクリプトの評価は行われない。最もよくある形式の事前エラーは構文エラー (syntax error) であり、これはソースコードが ECMAScript 文法でパースできないときに発生する。構文エラーは文法の定義に含まれておらず、暗黙に定義される。ES3 では新しい種類の事前エラー、例えばラベル付き break
文が字句的に囲まれていないラベルを参照することを報告するエラーなどが追加された。ES5 の strict モードでも事前エラーがいくつか追加された。こういったエラーの多くは仕様で「構文エラー」と定義されたものの、実際にはパースに失敗したエラーではなくパース結果が静的な意味論規則に違反したエラーである。ES6 より前の版でそういったエラーの多くは評価アルゴリズムの近くで形式でない散文を使って規定されていた。評価アルゴリズムが実行時におけるエラー条件を判定する疑似コードを持ち、条件が満たされた場合は事前エラーとして報告できる (あるいは報告すべき) ことが散文で規定されることもあった。
ES6 の機能は新しい種類の事前エラーを多く導入した。例えば let
宣言あるいは const
宣言を使って同名の識別子を複数定義しようとすると事前エラーとなる。ES6 では「静的意味論 (Static Semantics)」という項が各構文の文法を説明する節に追加され、事前エラーの条件を一貫した形で規定するようになった。図 43 に事前エラーの定義の例を示す。ここから分かるように、事前エラーの規則は静的意味論アルゴリズムを参照できる。静的意味論アルゴリズムは基本的に実行時アルゴリズムと同じように書かれるが、ECMAScript 環境の実行時状態は参照できない ── スクリプトの評価に先立って実行されるためである。静的意味論の事前エラー規則および静的意味論アルゴリズムはソースコードから実行することなく取り出せる情報だけを利用、解析できる。実行時アルゴリズムは静的意味論アルゴリズムを呼び出せるのに対して、静的意味論アルゴリズムは実行時アルゴリズムを呼び出せない。
13.3.1.1 静的意味論: 事前エラー
LexicalDeclaration : LetOrConst BindingList ;
- BindingList の BoundNames が "let" を含むなら、構文エラーである。
- BindingList の BoundNames が重複するエントリーを含むなら、構文エラーである。
LexicalBinding : BindingIdentifier Initializeropt
- Initializer が存在せず、この生成規則を含む LexicalDeclaration に対する IsConstantDeclaration が true なら、構文エラーである。
.........
13.3.1.3 静的意味論: IsConstantDeclaration
LexicalDeclaration : LetOrConst BindingList ;
- LetOrConst に対する IsConstantDeclaration を返す。
LetOrConst : let
- false を返す。
LetOrConst : const
- true を返す。
21.3 ES2015 の言語機能
wiki の Harmony 提案ページの最終バージョン [TC39 Harmony 2014] に挙げられた数十個の提案は言語あるいは標準ライブラリの新機能あるいは言語拡張として策定が進んだ。多くの場合で提案は複数回の反復があってから仕様のドラフトに取り入れられ、ドラフトに入った後に進化する提案もあった。いくつかの提案は検討から外されたり、将来の版に延期されたりした。
以降の節ではいくつかの重要な提案が開発された経緯を詳しく見ていく。またそれ以外の重要な機能の概要も述べる。
21.3.1 レルム、ジョブ、プロキシ、MOP
Harmony の目標には組み込みおよびホスト定義の奇異オブジェクトのセルフホストを可能にすること、そしてウェブブラウザが実装する意味論拡張を完全に規定することがあった。この目標に向けて、ECMAScript の「仮想機械」で使われる既存の抽象化の見直しと、新しい言語機能あるいは仕様が曖昧な言語機能を規定するのに利用できる新たな抽象化の追加が必要とされた。
「レルム (realm)」 [Wirfs-Brock 2015a, 72 ページ] は単一の ECMAScript 実行環境に含まれる複数のグローバル名前空間の意味論を記述するために新しく追加された仕様における抽象化である。レルムは HTML フレーム (§3.6) の意味論を記述するときに利用される。これはブラウザが実装する機能であるものの ECMAScript では ES1 から無視されていた機能だった。「ジョブ (job)」[Wirfs-Brock 2015a, 76 ページ] も新しく追加された仕様における抽象化であり、ECMAScript 実行環境が複数のスクリプトを逐次的に、一つずつ完了させながら実行する方式を決定的に定義する。ジョブはブラウザやその他の JavaScript ホストが提供するイベントディスパッチや遅延されたコールバックの意味論を説明する手段を提供する。ジョブは ES2015 におけるプロミス (promise) の意味論を定義するための基礎も提供した。
ES1 が提供した内部メソッド (§9) は事実上メタオブジェクトプロトコル (MOP) のようなものだった。内部メソッドは元々、組み込みオブジェクトとホスト提供オブジェクトに対するプロパティアクセスの観測可能な意味論の違いを仕様で記述するために追加された。しかし ES2015 より前の版における内部メソッドの意味論は不完全かつ曖昧で、その使い方も一貫していなかった。ホストオブジェクトを手なずけるため、奇異オブジェクトのセルフホストを可能にするため、そしてオブジェクト機能モデルのメンブレン語 [Van Cutsem and Miller 2013] をサポートするために、ES1–ES5 の内部メソッド設計は完全に規定された MOP に取って代わった。
JavaScript コードからの奇異オブジェクトの定義を可能にするには、その奇異オブジェクトの内部メソッドの実装を提供できる必要がある。この機能は ES2015 のプロキシオブジェクト [Wirfs-Brock 2015a, 495 ページ] によって提供される。ES42 では「キャッチオール (catch all)」 [TC39 ES4 2006a] と呼ばれるメカニズムが提案されており、これを使うと存在しないプロパティへのアクセスやメソッドの呼び出しを行ったときのデフォルト動作をオブジェクトごとに JavaScript コードから上書きできた。ES42 のキャッチオールは JavaScript 1.5 に存在した非標準の __noSuchMethod__
メカニズム [Mozilla 2008a] を改善したものとして計画されていた。Harmony では Brendan Eich [2009b; 2009d] が ES42 のキャッチオールを一般化し、「アクションメソッド」と彼が呼んだものをオブジェクトへ動的に関連付ける手法を発案した。特定の言語操作をオブジェクトに対して行うと、オブジェクトに定義されているアクションメソッドが存在すればそれが呼ばれるというものだった。利用可能な操作の集合は ES5 の内部メソッドと似ていたものの、完全に一致するわけではなかった。アクションが全てのプロパティアクセスに対して起動されるのか、それとも存在しないプロパティへのアクセスに対してだけ起動されるのかは決定していなかった。Eich が提案したオブジェクトにアクションメソッドを関連付ける API は、ES5 のオブジェクトリフレクション関数をモデルとしている:
var peer = new Object;
Object.defineCatchAll(obj, {
// 配列風の振る舞いを持つ length を実装するアクションメソッドを追加する
has: function (id) { return peer.hasOwnProperty(id); },
get: function (id) { return peer[id]; },
set: function (id, value) {
if ((id >>> 0) === id && id >= peer.length)
peer.length = 1 + id;
peer[id] = value
},
add: function (id) { Object.defineProperty(obj, id, {
get: function () { return peer[id]; },
set: function (value) { peer[id] = value);
})
},
// 他のアクションメソッドの定義...
});
この例では has
, get
, set
, add
というプロパティが defineCatchAll
を通してオブジェクト obj
に動的に関連付けられるキャッチオールのアクションを提供する。アクションを定義する関数は字句的に peer
オブジェクトへのアクセスを持つので、obj
と peer
の間には一対一の関係が確立される。これらの関数は peer
をバッキングストアとして利用し、obj
の固有プロパティに見えるものを提供する。またオブジェクト peer
の length
プロパティは動的に更新され、常にプロパティ名として使われた整数の最大値より 1 だけ大きい値となる。
Brendan Eich のキャッチオール提案のすぐ後に、Tom Van Cutsem と Mark Miller [2010a; 2010b] がチャンピオンを務めた別の設計が提案された。「プロキシに基づくキャッチオール」というタイトルで発表されたこの提案 [Van Cutsem 2009] は階層的なオブジェクトインターセッション API を定義する。このプロキシ提案が目指したのは仮想オブジェクトの定義を可能にすることだった。例えばオブジェクト機能ベースのセキュアなシステムにおける隔離機構で使われるメンブレンオブジェクトの定義である。TC39 はおおむねプロキシのストローマンに理解を示し、すぐに Harmony 提案として受理した。
この提案はプロキシオブジェクトという概念を導入する。仲介役のアクションメソッドでベースとなるオブジェクトを拡張するのではなく、ハンドラオブジェクトが関連付いたプロキシオブジェクトが作成される。ハンドラオブジェクトのメソッドは「トラップ (trap)」と呼ばれ、様々な言語操作によって起動される。ハンドラオブジェクトを使うと種々の言語操作に対するオブジェクトの振る舞いを完全に規定できる。トラップの動作は自己完結的にすることもできるし、字句的なキャプチャを通してハンドラから利用可能な既存のオブジェクトを利用することもできる。プロキシオブジェクトの例を示す:
// 簡単なフォワーディングプロキシ
function makeHandler(obj) { return {
has: function(name) { return name in obj; },
get: function(rcvr,name) { return obj[name]; },
set: function(rcvr,name,val) { obj[name]=val; return true; },
enumerate: function() {
var res = [];
for (name in obj) {
res.push(name);
}
return res;
},
delete: function(name) { return delete obj[name]; }
};
}
var proxy = Proxy.create(makeHandler(o), Object.getPrototypeOf(o));
この例で makeHandler
は引数に渡されたオブジェクトへの字句的なアクセスを持ったトラップからなるハンドラオブジェクトを作成するためのヘルパー関数である。makeHandler
に渡すのは新しく作成されたオブジェクトでも既存のオブジェクトでも構わない。前者の場合、新しいオブジェクトがキャッチオールの例における peer
と同様の役割を果たす。後者の場合、トラップは操作の一部もしくは全てを引数に渡した既存のオブジェクトに転送 (フォワーディング) できる。その場合は引数のオブジェクトが「フォワーディングプロキシ」の転送先 (ターゲット) としての役割を果たす。
トラップメソッドをハンドラオブジェクトに配置することで、ベースオブジェクトのプロパティとの名前の衝突が回避される。この提案は 7 つの基礎トラップ、6 つの派生トラップ5、2 つの関数オブジェクトに特有なトラップを定義した。キャッチオール提案と同様に、トラップは ES5 の内部メソッドと似てはいたものの完全に同じではなかった。ES5 では内部メソッド [[GetOwnProperty]] と [[DefineOwnProperty]] に関して破られてはいけない不変条件が存在した。ES2015 における厄介な問題の一つが、freeze あるいは seal されたオブジェクト、および設定不可能なプロパティをそういった不変条件を保ちながら仮想化することだった6。
オリジナルのプロキシ提案をプロトタイプした Van Cutsem [2011] は大きな改訂を発表した:
数週間前 Mark と私は顔を合わせ、プロキシに関するいくつかの未解決問題、特に設定不可能なプロパティや拡張不可能なオブジェクトの存在下でプロキシを上手く動作させる方法について話し合った。その結果として「ダイレクトプロキシ」と私たちが呼ぶものが生まれた: 私たちの新しい提案において、プロキシは必ず別の「ターゲット」オブジェクトのラッパーであるとされる。プロキシの考え方を少しだけ変えることで、未解決だった問題の多くが姿を消し、一部のケースではプロキシのオーバーヘッドが大きく削減される。
ダイレクトプロキシ提案 [2011a, b, 2012] では、ターゲットオブジェクト (次の例における o
) がフォワーディングプロキシの例における makeHandler
に渡されるオブジェクトと同じ役割を果たす。ターゲットオブジェクトはプロキシオブジェクトの内部状態として保持され、トラップが呼ばれると明示的な引数として渡される。プロキシオブジェクトはターゲットオブジェクトを知っているので、ターゲットオブジェクトに課される基礎的な不変条件を強制できる。ダイレクトプロキシを使ったバージョンのフォワーディングプロキシを次に示す:
// 簡単なダイレクトフォワーディングプロキシ
var proxy = Proxy(o, {
// ハンドラオブジェクト
has: function(target, name){ return Reflect.has(target, name) },
get: function(target, name, rcvr){ return Reflect.get(target, name, rcvr) },
set: function(target, name, val, rcvr){ return Reflect.set(target, name, val, rcvr) },
enumerate: function(target){ return Reflect.enumerate(target) },
// ...
});
Reflect
オブジェクトのメソッドは標準の内部メソッドに対応する。これらのメソッドを使うと、通常は JavaScript のコードシーケンスを通じて暗黙に呼び出されるオブジェクトの内部メソッドを直接呼び出すことができる。ダイレクトプロキシの当初の設計では ES5 の内部メソッドにほぼ対応する 16 個の異なるトラップが定義されていた。この設計は他にも、オブジェクトに対する内部操作であって内部メソッドとして定義されていないためにプロキシが捉えられないものをいくつか特定した。Tom Van Cutsem, Mark Miller, Allen Wirfs-Brock は ECMAScript 仕様およびホストオブジェクトが定義するオブジェクトの振る舞いを全て適切に表現できるよう協力して Harmony の内部メソッドとプロキシのトラップを改良した。この作業では新たな内部メソッドが追加されたり、プロキシで捉えられない操作が通常のトラップ可能なメソッド呼び出しに変更されたりした。また内部メソッドのそれぞれに対して必要不可欠な不変条件が定義された。ECMAScript 実装とホストにはこれらの条件を遵守することが要求され、プロキシはセルフホストの奇異オブジェクトに対してこれらの条件を強制する7。ES2015 の MOP の概要を図 44 に示す。
ES5 の内部メソッド | ES6 の内部メソッド | ES6 のプロキシトラップ と Reflect メソッド |
---|---|---|
[[CanPut]] | ||
[[DefaultValue]] | ||
[[GetProperty]] | ||
[[HasProperty]] | [[HasProperty]] | has |
[[Get]] | [[Get]] | get |
[[GetOwnProperty]] | [[GetOwnProperty]] | getOwnPropertyDescriptor |
[[Put]] | [[Set]] | set |
[[Delete]] | [[Delete]] | deleteProperty |
[[DefineOwnProperty]] | [[DefineOwnProperty]] | defineProperty |
[[Call]] | [[Call]] | apply |
[[Coustruct]] | [[Coustruct]] | construct |
[[Enumerate]] | enumerate | |
[[OenPropertyKeys]] | ownKeys | |
[[GetPropertyOf]] | getPropertyOf | |
[[SetPropertyOf]] | setPropertyOf | |
[[IsExtensible]] | isExtensible | |
[[PreventExtensions]] | preventExtensions |
ダイレクトプロキシの設計はカプセル化されたターゲットオブジェクトを利用するものの、ターゲットオブジェクトに対する容易で透過的なラッパーを提供することは意図されていない。その見た目に反して、プロキシはプロパティアクセスや「method not found」のログを取る簡単な手段ではない。そういった処理を持つプロキシをナイーブに実装すると、多くの場合で信頼性に欠けたりバグがあったりする。ダイレクトプロキシの中心的なユースケースはオブジェクトの仮想化とセキュアなメンブレンの作成である。Mark Miller [2018] は次のように説明した:
プロキシと WeakMap はメンブレンの作成をサポートするために設計され (そして最初に動機付けられ) た。スタンドアローンに使われるプロキシを透過的にすることはできず、透過性に近いものを合理的に達成することもできない。メンブレンはレルムの境界を透過的にエミュレートするのにごく近くなる。プライベートなメンバーを持つクラスに対しては、このエミュレートは本質的に完璧である。
21.3.2 ブロックスコープの宣言
ブロックスコープの字句的な宣言を加えることは一度目の ES41 の試みが始まったときから検討されていた。C 風の構文を持つ言語の経験があるプログラマは {}
で区切られたブロック内の宣言がそのブロックにローカルになると期待する。彼らにとってオリジナルの JavaScript 1.0 が持っていた var
のスコープ規則 (§3.7.1) は驚くべきものであり、深刻なバグを隠すこともあった。頻繁に発生したバグの一つが、ループに含まれるクロージャに関連する次のバグである:
function f(x) { // この関数にはバグがある
for (var p in x) {
var v = doSomething(x, p);
obj.setCallback(function (arg) { handle(v, p, arg) });
// バグ: このループで作られるクロージャの v と p は全て同じ束縛を指す。
// 束縛が反復ごとに作成されることはない。
}
}
このパターンはブラウザ DOM を操作するコードで非常によく現れた ── 経験を積んだ JavaScript プログラマでさえ、var
宣言がブロックスコープでない事実を忘れることがあった。
既存の var
形式の宣言をブロックスコープに変更すると既存のコードが壊れてしまう。ES42 の取り組みでは、多くのプログラマが期待するブロックスコープの宣言を let
と const
というキーワードで行うことで意見がまとまっていた。キーワード let
は改変可能な変数束縛を定義し、const
は不変な定数束縛を定義する。let
と const
はブロックでだけ使えるわけではなく、var
による宣言を書ける任意の場所で使えるとされた。ES42 設計グループは「let は新たな var」というスローガンが書かれた T シャツを作りさえした。Harmony は let
と const
による宣言というアイデアを受け継いだものの、ES42 の取り組みは意味論に関して多くの問題に答えていなかった。
ES5 では const
宣言の追加が検討され、ES5 の仕様にはブロックレベルの宣言による束縛の意味論を規定するのに利用できる抽象化が追加された。しかしそういった宣言の意味論を正確にどうすべきかは明らかでなかった。const
の意味論に関する問題が現れるコードスニペットを次に示す:
{ // 外側のブロック
let x = "outer";
{ // 内側のブロック
console.log(x);
var refX1 = function() { return x };
console.log(refX1());
const x = "inner";
console.log(x);
var refX2 = function() { return x };
console.log(refX2());
}
}
内側のブロックに存在する x
への参照で const
宣言の前にあるもの (のいずれか、もしくは全て) はコンパイル時エラーとなるべきだろうか? もしエラーでないなら、x
への参照は外側にある x
の束縛として解決するべきだろうか、それとも内側の x
として解決して、初期化されるまで undefined
とするべきだろうか? refX1
を const
宣言の前に呼ぶ場合と const
宣言の後に呼ぶ場合では、x
が指す束縛とその値は異なるだろうか? こういった疑問は内部のブロックにある宣言が let
である場合にも生じる。Waldemar Horwat [2008a] は、これらの参照に対して考えられる四つの意味論を示した:
A1. 字句的デッドゾーン: 同じブロックに含まれる宣言でテキスト上で定義より前にあるものはエラーとなる。
A2. 字句的ウィンドウ: 同じブロックに含まれる宣言でテキスト上で定義より前にあるものは外側のスコープに向かう。
B1. 時間的デッドゾーン: 同じブロックに含まれる宣言で時間的に定義より前にあるものはエラーとなる。
B2. 時間的ウィンドウ: 同じブロックに含まれる宣言で時間的に定義より前にあるものは外側のスコープに向かう。
Horwat は「デッドゾーン」の概念を議論に持ち込んだ人物として Lars Hansen の名前を挙げている。「時間的に前にある」とは実行時の評価順序のことを意味する。A2 と B2 は望ましくないとされた。なぜなら、ブロックに含まれる同じ名前が場所の違いだけで異なる束縛を指すことになる上に、B2 にいたっては同じ場所にある名前が実行するたびに異なる束縛を指す可能性が生じるからである。また A1 を採用すると let
や const
を使って相互再帰関数が書けなくなるので、A1 も望ましくないとされた。B1 には実行時の初期化チェックが全ての参照で必要になるという欠点があるものの、コンパイラは簡単な解析を通してチェックの多くを安全に削除できる。TC39 は二年近くの時間をかけて、新しい形式の字句的宣言は B1 の時間的デッドゾーン (temporal dead zone, TDZ) と呼ばれる意味論を持つべきだということに合意した。この意味論は次の規則でまとめられる:
- 単一のスコープ内で、任意の名前に対して束縛が一つだけ存在する。
let
,const
,class
,import
, ブロックレベルの関数宣言、そして仮パラメータの束縛は実行時に初期化されるまで死んでいる。- 未初期化の束縛に対するアクセスと代入は実行時エラーである。
仕様において、一つ目の規則は事前エラー規則として記述され、他の二つの規則は実行時意味論アルゴリズムの中に記述される。
Allen Wirfs-Brock が let
と const
を仕様に取り込み始めたとき、彼はレガシーの var
や関数宣言との相互作用が起こり得ることを発見した。この発見から TC39 で議論がもう一ラウンド沸き起こり、最終的に次の規則の追加に合意があった:
- 入れ子になったブロックの任意の階層に同じ名前の
var
宣言が複数存在しても構わない。同じ名前を持ったvar
宣言は同じ束縛を指し、その束縛は全てのvar
宣言を囲む最も狭い関数のスコープあるいはグローバルのトップレベルスコープに巻き上げられる (ES1 から続くレガシーな意味論)。 - 複数の
var
宣言と関数/グローバルのトップレベルfunction
宣言が同じ名前を持って構わない。名前ごとに一つの束縛が作られる (ES3 から続くレガシーな意味論)。 - これ以外の形で一つのブロック内に同名の宣言が複数ある場合、事前エラーとなる:
var
/let
,let
/let
,let
/const
,let
/function
,class
/function
,const
/class
など。 - ブロックレベルの
var
で宣言される名前を巻き上げるときに、外側にある同名のlet
,const
,class
,import
あるいはブロックレベルのfunction
宣言を飛び越す場合、事前エラーとなる。 var
宣言に対応する束縛は作成されるときにundefined
へ自動的に初期化される。そのためvar
で宣言された束縛にアクセスするときに TDZ の制限は存在しない。
グローバル宣言の扱いについても懸案事項がいくつかあった。ES2015 より前の版では、全てのグローバル宣言はホスト環境が提供するグローバルオブジェクト (§3.6) にプロパティを作成する。しかしオブジェクトのプロパティには、時間的デッドゾーンの実現に必要なプロパティに未初期化のタグを付ける機能が存在しない。新しい const
, let
, class
宣言がグローバルなレベルに現れたときは var
宣言であるかのように扱うという提案もあった。この方式には先例があり、ES2015 より前のブラウザの一部は const
宣言をこのように実装していた。しかしそうすると、グローバルなレベルとそれ以外の場所での利用において新しく追加される宣言から一貫性が失われる。TC39 はこの方式を取らず、その代わり字句的宣言の規則を可能な限り全ての場所で一貫させることで合意した。グローバルスコープにおいて、var
宣言と function
宣言はグローバルオブジェクトにプロパティを作成するレガシーな振る舞いとなる。一方で他の形式の宣言は全てグローバルオブジェクトのプロパティと関係のない字句的束縛を作成し、上述の新しい規則が var
と let
の名前の衝突を禁止する。ただし var
あるいは function
で作られていないグローバルオブジェクトのプロパティとは名前の衝突が起こらないとされた。そういった場合はグローバルな let
/const
/class
宣言が同名のグローバルオブジェクトのプロパティを隠蔽する。この規則により、新しい宣言を使って定義されたグローバル変数を個別のスクリプトで複数回定義することはできなくなった。
ブロックスコープの let
宣言と const
宣言を追加するだけではループでクロージャを使ったときのハザードを回避するのに十分でない。for (var p in x)
が作成する変数 p
のスコープにも問題がある。ES2015 において、この問題は for
文の var
の場所に let
と const
を使えるようにすることで解決された。この場所で let
または const
を使うと、ループ本体が反復されるたびに本体のスコープ輪郭に含まれる束縛が新しく再作成される。例えば、ループ for (const p in x) {body}
はおおよそ次のように脱糖語される:
// for (const p in x) {body} の大まかな脱糖
{
let $next;
for ($next in x) {
const p = $next;
{body}
}
}
C スタイルの三つの式を取る for
文が作成する字句的束縛の扱いはさらに複雑で、さらなる議論を呼んだ。JavaScript 1.0 にはこの形式の for
文において一つ目の式に var
宣言を使える機能が存在したので、let
宣言と const
宣言も同様に使えるのが望ましいとされた。しかし、そういった宣言が作成する束縛はどのように扱われるべきだろうか? for
文全体と同じ寿命を持つ単一の束縛を作成するべきだろうか、それとも for-in
文と同様にループの各反復で個別の束縛を作成するべきだろうか? よく使われるコーディングパターンでは二つ目の式と三つ目の式、あるいはループ本体において一つ目の式が宣言するループ変数を更新して次の反復で利用するので、答えは明らかでなかった。もし各反復でループ変数が新しい束縛となるなら、前回の反復の最後におけるループ変数の値を使って次の反復のループ変数を自動的に初期化する処理が必要になる。C 風の言語の多くは for
文ごとに単一の束縛を作成するアプローチを取っており、反復ごとに束縛を作成することはしない。これは ES6 仕様の初期ドラフトでも同様だった。しかし、この C 風のアプローチではループでクロージャを使ったときのハザードを回避できない。これが理由となり、最終的に三つの式を持つ for
文で let
宣言を使ったときは反復ごとに新しい束縛が用意され、反復をまたぐときに値が自動的に受け渡される方式に変更された。ただしループ変数が const
宣言で作成されたときは for
のヘッダーあるいはループ本体の他の式で値が変更されないので、for
文ごとに単一の束縛で十分となる。
もう一つの大きな問題が文ブロック内で宣言された関数の意味論だった。ES3 はブロック内における関数宣言を構文的にも意味論的にも意図的に規定しなかった (§12) にもかかわらず、実装はこの指針を無視してブロック内での関数宣言を許した ── 残念なことに、主要なブラウザはこの宣言にそれぞれ異なる意味論を与えた。しかし意味論が重なる部分もあった [Terlson 2012] ので、一部のユースケースではブロック内での関数宣言とその関数の利用を全ての主要なブラウザで相互運用可能になるように行うことができた。一方で、ES2015 の字句的な宣言規則の下ではそういったユースケースの一部が違法あるいは異なる意味を持つことになる事実も判明していた。このため、そういったケースに対して新しい規則を実装すると「ウェブが壊れる」ことになる。ES5 の strict モードではブロックレベルの関数宣言が禁止されていたので、この問題は strict モードのコードでは発生しない。非 strict モードのコードに対する一つのアプローチは、ES3 の例に従いブロックレベルの関数宣言に関して何も定義しないというものだった ── ブロックレベルの関数宣言と新しい字句的な形式の宣言を統合させるかどうか、あるいはどのように統合させるかを実装に任せるということである。しかし、こうすると相互運用性は向上せず、1JS の目標 [TC39 2013b] に反してしまう。TC39 [TC39 2013a] はその方法を取らず、代わりに既存のブロックレベルの関数が意味のある形で相互運用でき、かつ新しい規則の下でエラーになるケースは少数しかないことを特定した。例えば次のようなケースである:
function f(bool) {
if (bool == true) {
function g() { /* 何かする */ }
}
if (bool == true) g(); // これは主要なブラウザの全てで動作した
}
この問題を修正するために、こういった相互運用可能なユースケースを静的に検出し、それを適法として上でレガシーなウェブページと互換にする非 strict モードのコードにおける規則が追加された。上記の例であれば、この規則は上記のコードを次のようなコードへと変換する:
function f(bool) {
var g; // ただしトップレベルに g の let 宣言があれば事前エラー
function $setg(v) { g = v }
if (bool==true) {
function g() { /* 何かする */ }
$setg(g); // トップレベルの g の値をローカルな g に変更する
}
if (bool==true) g(); // トップレベルの g を参照する
}
21.3.3 クラス
Harmony の取り組みが開始された 2008 年 7 月の TC39 会合では、クラスを仕様に含めるべきかどうか、そしてクラスを含めるならどのように行うべきかの議論に多くの時間が費やされた。ES4 の二度の取り組みでは洗練されたクラスの構文と意味論の作成に大きな労力が注がれ、どちらの設計もクラスのサポートのために新しい実行時メカニズムを必要とした。これらの設計は大きく分けると「Java に影響されたクラス (Java-inspired class)」に分類される。
Mark Miller [2008d] はラムダ関数と字句的キャプチャを用いたテクニックを使ってクラス風の抽象化を実装するのに必要な実行時メカニズムを ES3 は既に持っていると主張した。このテクニック [Dickey 1992; Sussman and Steele Jr 1975] は Scheme で利用されるものであり、Douglas Crockford [2008b, 52–55 ページ] が JavaScript 向けに改造したものを示していた。ラムダ関数によるクラス定義の脱糖というこのスタイルは本質的にモジュールパターン (§13.2) と同じであり、クラスとは何度もインスタンス化されることが意図された軽量の小さなモジュールに過ぎないという事実がここから示唆される。Miller はこのアプローチを「砂糖としてのクラス (class as sugar)」と呼んだ。
Cormac Flanagan [Flanagan 2008] はクラスに関する初期の議論を次のようにまとめた:
EcmaScriptママ にはデータ抽象化と隠蔽を持った高整合性オブジェクト8、そしてプライベートなフィールドとメソッドのより良いサポートが必要である...
... 私たちはまず単純でミニマリストな設計に集中する。この設計は継承と型注釈を持たず、インスタンスごとのプライベートデータを持つ。クラスの名前向けの個別の名前空間は存在せず、クラスオブジェクトは新しい種類の (ファーストクラスの) 値である。
Flanagan のストローマンはクラスの定義に次の簡単な構文を使った:
class Point (initialX, initialY) {
private x = initialX;
private y = initialY;
public getX() {return x};
public getY() {return y};
}
Cormac Flanagan の提案には完全な脱糖が含まれておらず、意味論的な詳細がほとんど書かれていなかった。これに対抗する形で Mark Miller [2008c; 2009; 2010a] は同様の表層構文を持った設計を提案した。Miller の提案は完全な脱糖を持ち、クラスのインスタンスのために新しい種類の実行時オブジェクトを必要としなかった。全てのメソッドとインスタンス変数はインスタンスごとに字句的にキャプチャされた宣言として表され、クラスの定義の本体でのみ直接アクセス可能となる。クラスのインスタンスオブジェクトのプロパティがパブリックメソッドへの外部アクセスを提供し、パブリックなインスタンス変数には get
アクセッサが提供された。インスタンス変数に対する直接的な外部代入は許されず、this
キーワードは利用されなかった。
Mark Miller の「砂糖としてのクラス」提案に対して繰り返された批判が、オブジェクトを作りすぎるというものだった。\(n\) 個のメソッドを持つクラスをインスタンス化するたびに、実際のインスタンスオブジェクトとは別にインスタンス固有のクロージャオブジェクトが \(n\) 個作成される。Miller の意見は、脱糖は観測可能な意味論として定義されたものであり、実装はクロージャオブジェクトの生成を避けるテクニックを使って構わない、というものだった。しかし TC39 の懐疑的なメンバーは実装がそういった最適化を行わないのではないかと疑問視した。もう一つの懸念は振る舞いを合成する継承といったメカニズムが存在しないことであり、これを受けて Miller は自身の脱糖を利用するクラス設計に合成可能なトレイト (Trait) を組み込んだ提案を作成した。
敵対的なウェブ広告などのウェブマッシュアップ (§20.1.3) がプライベートな情報を盗む可能性がある事実を一番の問題と考えていた TC39 のメンバーにとって、高整合性オブジェクトの定義をサポートすることは最優先の事項だった。この問題は TC39 全体で共有されていたものの、優先度に関してはそうでなかった。Waldemar Horwat [2010] は自身が執筆した 2010 年 9 月の TC39 会合の報告書に次の観察を記している:
目標に関してグループ内で党派分裂: 「高整合性」vs.「人々が既に書いているものをより良い構文で支援する」vs. もしかしたら両方いけるかもしれない。
Allen Wirfs-Brock はオブジェクトの作成における手続き的な部分を減らせば二つ目の目標の役に立つのではないかと考えた。古典的な JavaScript においてクラスに最も近いのはコンストラクタ関数 (§3.3) であり、コンストラクタ関数は新しいオブジェクトのプロパティを手続き的に定義する。オブジェクトリテラルはオブジェクトのプロパティを定義するためのより宣言的な手段を提供したものの、ECMAScript のこれまでの版における組み込みクラスで使われる特殊な機能9を容易に可能とするために必要なアフォーダンスは持っていなかった。「クラス」を新種の言語要素として導入せずとも、オブジェクトリテラルを拡張して人々が現在書いているものに対するより良いサポートを提供した方がいいのではないか、というのが彼の意見だった。
関連する提案のグループで、Wirfs-Brock [2011c; 2011d] はオブジェクトリテラルを拡張する方法の一つを示した。この拡張ではオブジェクトリテラルがより宣言的になり、ES5 のオブジェクトリフレクション API を使ってオブジェクトを定義するお決まりのコードが必要なくなる。拡張されたオブジェクトリテラルの機能を使うファクトリ関数語が明示的なプロトタイプ、メソッド、プライベートプロパティを持つクラスを定義するコードを図 45 に示す。
function tripleFactory(a,b,c) {
return { // このオブジェクトリテラルは三つ組 (triple) オブジェクトを作成する
// メタプロパティ proto が継承プロパティを設定する
<proto: Array.prototype,
// メタプロパティ sealed を付けると Object.seal() が適用される
sealed>,
0: a,
1: b,
2: c,
// var: [[enumerable]] を false に設定する
// const: [[writable]] を false に設定する
var length const:3,
// method は関数の値を持つデータプロパティを表し、[[enumerable]] は false となる
method toString() {
return "triple(" + this[0] + "," + this[1] + "," + this[2] + ")"
},
method sum() {
return this[0] + this[1] + this[2]
}
}
}
Allen Wirfs-Brock の提案は拡張オブジェクトリテラルの構文がクラス定義の本体として利用できることも指摘した。Wirfs-Brock [2011a] は 2011 年 3 月の TC39 プレゼンテーションで、これまでの全ての ECMA-262 における第 15 節10で説明される組み込みライブラリのクラスで使われた基本的な三つ組「コンストラクタ関数、プロトタイプオブジェクト、インスタンスオブジェクト」がクラス定義によって生成されるべきだと提案した。クラスの脱糖先はラムダ式 (砂糖としてのクラス) でも新種の実行時要素 (Java に影響されたクラス) でもなく、JavaScript プログラマとフレームワーク開発者によって既に利用され周知されているコンストラクタ関数とプロトタイプ継承関連のオブジェクトであるのが望ましい、ということである。会合では拡張オブジェクトリテラルの構文の様々な詳細について意見の大きな食い違いがあったものの、クラス定義の中心的な意味論を仕様第 15 節にある「コンストラクタ、プロトタイプ、インスタンス」の三つ組とするべきだということには緩い合意があった。
2011 年 5 月の初め、TC39 の ES.next 機能凍結会合が目前に迫る中、依然として競合するクラス関連のストローマン提案がいくつか存在しており、いずれかの提案を採用するコンセンサスが得られるかどうかは見通せない状況だった。2011 年 5 月 10 日、Allen Wirfs-Brock は Mark Miller, Peter Hallam, Bob Nystrom と話し合いを行った。Hallam と Nystrom は JavaScript のクラスサポートを Google のトランスパイラ Traceur [Traceur Project 2011b] を使ってプロトタイプするチームのメンバーであり、彼らのプロトタイプは Wirfs-Brock の提案と Miller の提案の両方からアイデアを取り入れていた。Bob Nystrom [2011] は会合の報告書で意見が一致した様々な点を示した。例をいくつか示す:
... コンストラクタ関数、プロトタイプ、インスタンスの三つ組は他の言語でクラスが解決する問題を JavaScript で解決するのに十分以上である。Harmony におけるクラス構文の意図はそういった意味論を変えることではない。そうではなく、元々の意味論に対する簡潔かつ宣言的な表層構文を提供することで内部の手続き的な処理ではなくプログラマの意図がコードに現れるようにすることである。
... オブジェクトは宣言的で、豊富な情報を持つ。関数は手続き的で、振る舞いが記述される。クラスに関する問題は、「これらの抽象化からどれかを選んでその上にクラスを作るのか? もしそうなら、どちらか?」である...
我々のコンセンサス提案は、両方を使うことでこの宗教的不和を解消する: クラス本体そのものに対してはオブジェクトリテラル風の形式を、コンストラクタに対しては関数を使う。
会合の後 Mark Miller [2011b] は新しいストローマンを作成し、それはコンセンサスに達していない詳細が多くあったにもかかわらず機能凍結会合 [TC39 2011b] で受理された。図 46 に示すクラス定義の例は機能凍結会合のために Miller が執筆したクラス提案から取ったものである。
class Monster extends Character {
constructor(name, health) { // コンストラクタ関数
super(); // 上位クラスのコンストラクタ関数を呼び出す
public name = name; // パブリックなインスタンスプロパティ
private health = health; // プライベートなインスタンス変数
}
attack(target) { // プロトタイプメソッド
log('The monster attacks ' + target);
}
get isAlive() { // プロトタイプ get アクセッサ
return private(this).health > 0;
}
set health(value) { // プロトタイプ set アクセッサ
if (value < 0) {
throw new Error('Health must be non-negative.')
}
private(this).health = value
}
public numAttacks = 0; // プロトタイプデータプロパティ
public const attackMessage = 'The monster hits you!'; // 読み込み専用
}
一か月後、Dave Herman [2011c] は es-discuss に「最小限のクラス」と題されたスレッドを立て、クラス提案の複雑さとクラス提案に関して合意できていない多くの点が ES.next のスケジュールに対するリスクになっているという懸念を表明した。彼は現在の設計に代わるものとして、プロトタイプ継承、コンストラクタ、宣言的メソッド、super
キーワードによる継承されたメソッドの呼び出しだけを持ったクラス宣言からなる設計を提案した。除外されたのは宣言的プロトタイプ、コンストラクタプロパティ、プライベートデータ、および議論を呼んでいた全ての機能である。Herman の提案は 2011 年 7 月の会合 [TC39 2011a] で議論されたものの、TC39 は当時検討されていた Mark Miller による提案に関する未解決の問題を解決することに集中する決断を下した。Brendan Eich [2012a] は後に次のように記している:
昨年の夏のレイモンドでは「最小限のクラス」を支持する TC39 メンバーも多くいた。しかし
const
とガードに対する初期化前使用の詳細を詰める中で問題が起こって...
クラスの別設計に関するオンラインの議論 [Ashkenas 2011; Eich 2011a; Herman 2011a] が続いたことに動機付けられ、Dave Herman は「最小限のクラス」の新たなストローマンを作成した。この提案は基本的に Herman の以前の投稿を定式化したものだったが、他に「スタティック」なコンストラクタデータとメソッドプロパティが追加されていた。次の二回の TC39 会合で Herman の提案はほとんど議論されず、最終的な計画における意見の食い違いの解決に向けた進捗はほとんど無かった。Brendan Eich [2012c] は問題を次のように説明した:
... Waldemar [Horwat] が指摘した一般的な傾向は本当だ: 小さすぎれば意味が無く、大きすぎれば合意できない。私たちには「ほどほど」のクラス ── 適温適量のクラス ── が必要だ。
2012 年 3 月の初め、 es-discuss コミュニティのメンバーは TC39 が ES.next のクラス設計を完了させられていないという明白な事実に対して募らせた不満を表明し始めた。Russel Leggett [2012] は「クラスの "滑り止め11構文"の模索」と題されたスレッドで次の質問を投げかけた:
無いよりはましだと私たち全員が同意できて、さらに重要なこととして、将来に改善できる可能性を残しておけるようなクラス構文を考案することはできないだろうか? 「滑り止め構文」と言っても、これはより良い構文の模索を止めることを意味しない。私たちが今見つけられないなら、これから何か ── ES7 で改善できる何か ── が見つかるだろうということを意味するだけだ。
Leggett の投稿には 3 日間で 119 個の主に好意的な反応が付いた。彼の投稿には「絶対最小要件」が示されており、これは本質的に Dave Herman が昨年の夏にまとめた機能リストと同じだった。Leggett の貢献は「滑り止め」という比喩を持ち出したことである。Allen Wirfs-Brock はすぐに支持を表明し、Herman の「最小限のクラス」提案を滑り止めの比喩を使って再構築した「最大限に最小限」なバージョン [Wirfs-Brock 2012d] を新しく作成した。最も重要な技術的変更はコンストラクタプロパティを提案から削除したことだった12。「最大限に最小限」提案を 2012 年 3 月の TC39 会合の議題に含めるには遅すぎたものの、Allen Wirfs-Brock と Allex Russel は会合の終わりに非公式の議論を始めた [TC39 2012a]。反応はおおむね好意的だった。しかし数人のメンバーは提案が小さすぎて行う意味が無いのではないか、あるいは彼らが考えた将来の拡張で障害になるのではないかという懸念を表明した。この提案に関して合意を取ることはなかったが、Wirfs-Brock と Russell はこれより手の込んだものが ES.next に含まれる可能性は低いという意見を述べた。
「最大限に最小限」提案は 2012 年 5 月の会合で正式な議題となり、同じような議論から同じような結論が得られた [TC39 2012b]。出席者はコンセンサスへと徐々に進んだものの、一部の重要人物が出席していなかった。スケジュールのプレッシャーがあったので、プロトタイプと参考仕様ドラフトに取り掛かっても構わないだろうという合意があった。7 月の会合 [TC39 2012c] までの間に、Allen Wirfs-Brock は最大限に最小限なクラスの仕様文書を執筆し、彼が出くわした全ての設計判断をまとめたプレゼンテーションスライド [Wirfs-Brock 2012b] を作成した。彼はそれぞれの設計判断の概要を TC39 に説明し、受理あるいは他の選択肢に対するコンセンサスを記録していった。このアプローチは提案そのものに関するコンセンサスを脇に置いていたが、TC39 は細かな設計レベルでの合意形成に集中した。ES.next 仕様の次のドラフト [Wirfs-Brock et al. 2012b, c] には、7 月の会合で行われた判断を含んだ最大限に最小限なクラスの完全な設計が取り込まれた。これに反対する者はいなかった。
しかし 2014 年の夏にブラウザの JavaScript エンジン開発者が ES6 のクラスを実装し始めると、大きな問題が発見された。ES6 の活動が長年にわたって掲げてきた目標の一つに、Array
[Kangax 2010] やウェブプラットフォームの DOM クラスに対して「サブクラス化」の手段を提供することがあった。Allen Wirfs-Brock [2012c; 2012e] は組み込みのコンストラクタをサブクラス化する伝統的な JavaScript のアプローチに問題がある理由を説明した Harmony ストローマンを執筆していた。組み込みのコンストラクタは C++ などの実装言語で定義されることが多い。実装言語で書かれたコンストラクタはプライベートなオブジェクト表現のアロケートと初期化を行い、通常その特有な構造は関連付いた組み込みメソッドしか知らない。そして組み込みのメソッドもたいてい実装言語で実装されている。この仕組みは組み込みのコンストラクタが new
演算子で直接呼び出されるときは正しく動作するものの、そういったコンストラクタを JavaScript のプロトタイプを通したアドホック継承スキームで「サブクラス化」したときは new
演算子がサブクラスのコンストラクタ (JavaScript で書かれることが多い) を呼び出すので、継承元の組み込みメソッドが期待するプライベートなオブジェクト表現ではなく通常のオブジェクト表現がアロケートされることになる。Wirfs-Brock [2013] は最大限に最小限なクラスの意味論を規定するとき、new
の意味論をアロケーションフェーズと初期化フェーズに分けることでこの問題を回避しようとした。new
は最初に @@create
という特別な名前のメソッドを起動してオブジェクトをアロケートし、このメソッドは通常オーバーライドされない。オブジェクトの初期化はアロケーションの後に起こり、サブクラスのコンストラクタによって統括される。初期化は通常 super
を呼び出してスーパークラスのコンストラクタを起動することでスーパークラスが必要とする固有の初期化処理を行い、それからサブクラス固有の初期化を行う。コードが適切に書かれれば、この仕組みにより組み込みのスーパークラスがユニークかつプライベートな表現をアロケートできるようになる。その後アロケートされたオブジェクトはサブクラスのコンストラクタに渡され、そのコンストラクタの初期化コードはスーパークラスが提供したオブジェクトにサブクラスのプロパティを追加できる。
2014 年に判明した問題は、@@create
メソッドが作成するオブジェクトが初期化されていないために、バグを含む、あるいは悪意あるサブクラスのコンストラクタが組み込みのスーパークラスメソッド (C++ で実装される可能性が高い) を未初期化のオブジェクトに対して呼び出す ── そして破滅的な結果に繋がる ── 可能性があることだった。Wirfs-Brock はそういった問題が起こる全てのオブジェクトに初期化状態を内部で管理させ、対応する組み込みメソッドに未初期化のオブジェクトを受け取っていないことを確認させればよいと思っていた。しかし Mozilla の Boris Zbarsky [Zbarsky 2014] は、ブラウザにはそういったメソッドが数千個存在するので、二つのフェーズを利用するアプローチを使うとそういったメソッドの全てに対して DOM 仕様と全ブラウザの実装を更新する必要が生じると指摘した。この指摘に動機付けられ、アロケーションと初期化を単一フェーズで行うアプローチを取る提案 [Wirfs-Brock et al. 2014c, d] と、二つのフェーズを使いながらもコンストラクタの引数を @@method
とコンストラクタの両方に渡すアプローチを取る提案 [Herman and Katz 2014] が作成された。2014 年の残りを通じてこれらの提案と他の選択肢に関する議論は白熱し、コンセンサスの欠如により 2015 年 6 月の ES6 公開は延期されるかクラスが ES6 から完全に削除されるのではないかとしばらくの間思われていた。しかし 2015 年 1 月、TC39 は単一フェーズアプローチの亜種 [TC39 2015a; Wirfs-Brock 2015b] を採用するというコンセンサスに達した。この経験により、ES7 以降の新しい機能では実装者からのフィードバックをこれまでより早期にこれまでより多く要求しようという TC39 の決意は堅くなった。
21.3.4 モジュール
ES4 の設計が持っていた複雑な側面の一つが、大規模なプログラムやライブラリを構造化するためのパッケージと名前空間の構文だった。ES42 が破棄されるころにはこういった仕組みが持つ大きな問題が発見されており [Dyer 2008b; Stachowiak 2008b]、この仕組みが Harmony に適さないことは明らかだった。当時、モジュールパターン (§13.2) を使ってアドホックなモジュール性を達成するソリューションが影響力を持つ JavaScript 開発者によって採用されていた [Miraglia 2007; Yahoo! Developer Network 2008]。2009 年 1 月、Kris Kowal と Ihab Awad はモジュールパターンに影響を受けた設計 [Awad and Kowal 2009; Kowal and Awad 2009a] を TC39 [2009c] で発表した。彼らの設計は後に Node.js によって利用される CommonJS モジュールシステムとなった。
Kris Kowal と Ihab Awad はオリジナルの提案と以降の改訂版 [Kowal-2009b; Kowal and Awad 2009b] において、提案されたモジュール設計を動的意味論は変えずに糖衣構文で変更した別の選択肢をいくつか示した。Awad [2010a; 2010c] はその後、Secure ECMAScript の Caja Project [2012] で利用されていた E 言語 [Miller et al. 2019] の Emaker モジュール、および CommonJS から着想を得た異なる提案を作成した。こういった提案でモジュールは新しい計算的抽象化の仕組みを提供する動的に構築可能なファーストクラスの実行時要素だったので、こういった提案は TC39 内で「ファーストクラスモジュールシステム」と呼ばれた。例えば Awad の提案では、異なるパラメータ値を持ってインスタンス化された同じモジュールの異なるインスタンスが複数同時に存在できた。
Brendan Eich [2009c] は異なるアプローチを示した:
Harmony におけるもう一つの選択肢は、プログラムが (実行されるときではなく) パースされるときに解析できる特別な構文 (例えば
import
ディレクティブ) だ。こうすると実装は依存するプログラムを全て事前に読み込めるので、モジュールのインポート (あるいは後で起こるデータ依存) によるブロックを避けられる。こうしないと、JS の「完了するまで実行する」という実行モデルを保持するために不格好なノンブロッキングのインポートが必要になる。
この代替案は「静的モジュールシステム」あるいは「セカンドクラスモジュールシステム」と呼ばれた。このモジュールシステムが提供するのはアプリケーションのコードを構造化する仕組みであり、新しい計算的抽象化を定義する仕組みではない。Sam Tobin-Hochstadt [2010] は次のように説明した:
... 状態が存在する言語では、振る舞いを変えることなくプログラムをモジュールに分割できるのが望ましいはずだ。コードのある部分が状態を持っているとして、その部分を個別のモジュールへと移動させたときに起こる変更は、他のリファクタリングと同程度であるべきである。もし新しい状態を何度も作成する必要があるなら、ES はそのための素敵なメカニズムも提供している。同様に A をインポートする単一のモジュールがあって、そのモジュールを二つに分け、分かれた二つがどちらも A をインポートするなら、このリファクタリングはプログラムの動作を変えるべきではない。
Dave Herman と Sam Tobin-Hochstadt はセカンドクラスの Harmony モジュールをまとめた「シンプルモジュール (Simple Module)」の設計を作成した [Herman 2010b, c, f; Herman and Tobin-Hochstadt 2011; Tobin-Hochstadt and Herman 2010]。基本的なアイデアは、モジュールは字句的束縛を共有できるコードの単位であるというものだった。コードの単位を区別し、どの束縛が共有されるかを指定するために特殊な構文が使われた。TC39 は二つのアプローチの利点を徹底的に議論し、最終的には Awad [2010b] が Herman/Tobin-Hochstadt の提案に労力を集中させることを TC39 に勧めた。
Herman/Tobin-Hochstadt の設計ではモジュール宣言がモジュールに割り当てられる字句的識別子を持ち、識別子の後にモジュールのコード本体あるいはモジュールのコードを持つ外部リソースを指定する文字列が続く。モジュールの外側に露出される束縛には宣言の前に export
キーワードが付けられる。この例を示す:
module m1 { // 内部モジュール
export var x = 0, y=0;
export function f() {/* ... */};
}
module m2 { // 同じソースファイルにある別の内部モジュール
export const pi = 3.1415926;
}
// 文字列リテラルが外部モジュールを指定する
module mx = load "http://example.com/js/x.js";
// ... m1, m2, mx に含まれる束縛をインポートして利用するコード
モジュール宣言は入れ子にすることもできた。x.js
のような外部モジュールはモジュール本体のコードだけからなり、それを囲むモジュール宣言構文は付かない。import
宣言はモジュールがエクスポートする束縛をインポートするモジュールから字句的にアクセス可能にするために利用される。上記の例に続くコードは例えば次のような import
を持つ:
import m1.{x, f}; // m1 からエクスポートされる束縛を二つインポートする
import m2.{pi: PI}; // 束縛をインポートし、ローカルでのアクセスのため名前を変える
import mx.*; // mx がエクスポートする束縛を全てインポートする
import mx as X; // mx がエクスポートする束縛をプロパティに持つオブジェクトに対する
// ローカルな束縛 X を作成する
モジュール宣言、文字列リテラルを利用した外部モジュール指定子、宣言的な export
/import
の定義によって、 字句的束縛を共有する相互独立なモジュールの集合をコードの実行に先立って静的に決定し、それらをリンクすることが可能になる。循環依存は許される。実行が開始されると、モジュールは指定された決定的な順序で初期化され、循環依存が初期化できない場合は時間的デッドゾーンが実行時エラーを保証する。
それから構文は進化した [Herman et al. 2013] ものの、共有される字句的束縛を持った静的リンク可能なモジュールという基本的なアイデアは保持された。大きな変更の一つは明示的なモジュール宣言構文、モジュール識別子、内部 (入れ子) モジュールの削除である。Harmony モジュールはソースファイルごとに定義され、リテラル文字列のリソース識別子によって指定されることになった。モジュール識別子の削除によって import
の構文に変更が必要になった。またワイルドカードで字句的束縛を個別に全てインポートする機能はエラーを招く可能性が高すぎるとして削除され、ワイルドカードによるインポートはプロパティに束縛を持つ変更可能な単一の名前空間オブジェクトを使う方式に変更された。最終的な構文を使って上記の import
の例を書き直すと次のようになる:
import {x, f} from "m1.js"; // m1 が公開する束縛を二つインポートする
import {pi as PI} from "m2.js"; // 束縛をインポートし、ローカルでのアクセスのため名前を変える
import * as X from "mx.js"; // X を名前空間オブジェクトへローカルに束縛する
// X のプロパティは mx.js がエクスポートする束縛に対応する
// 新しく追加された import の形式
import from "my.js"; // my.js を初期化処理だけを行うためにインポートする
import z from "mz.js"; // mz.js がエクスポートする単一のデフォルト束縛をインポートする
モジュール宣言の削除とデフォルト束縛のインポートの追加は設計の終盤で起こった変更だった。当時 Node.js が予想外に速く採用され、Node.js を通して CommonJS モジュールが JavaScript 開発者コミュニティに広く知られるようになっていた。TC39 はコミュニティからネガティブなフィードバック [Denicola 2014] を得ており、CommonJS モジュールがデファクトスタンダードになることで Harmony の設計を見劣りさせてしまうのではないかという懸念を持っていた。デフォルトエクスポートの形式は CommonJS モジュールの多くが採用していた単一エクスポートのパターン13に慣れていた開発者のために追加された。TC39 でモジュールを担当したチャンピオンは Node.js 開発者に Harmony モジュールを広める活動 [Katz 2014] も開始した。
最初のシンプルモジュール提案には読み込まれているモジュールの意味論を実行中の JavaScript プログラムに提供するモジュールローダー [Herman 2010e] という概念が存在した。その意図は言語レベルのモジュールの構文と意味論、モジュール読み込みの実行時意味論、そしてモジュールローダー API を ECMAScript 仕様で定義し、モジュールローダー API を通してローダーの意味論を制御、拡張する仕組みを JavaScript プログラマに提供することだった。モジュールの読み込み処理は最終的に 5 つのステージからなるパイプラインとして構想された。その 5 つのステージとは正規化 (normalize)、解決 (resolve)、フェッチ (fetch)、変換 (translate)、リンク (link) である。ローダーは最初にモジュールの識別子を正規化し、その後モジュールのソースコードを取得し、前処理を行い、モジュールの相互依存関係を確定させ、インポートとエクスポートをリンクし、最後に相互依存するモジュールを初期化する。この仕組みはモジュールローダーの柔軟性を最大限まで高め、ウェブブラウザの非同期 I/O モデルを完全にサポートするためのものだった。JSConf 2011 で Dave Herman は概念実証のモジュールローダーのデモ [Leung 2011] を行った。このローダーは変換ステージが拡張されており、CoffeeScript と Scheme のコードを JavaScript ベースのウェブページのモジュールとして読み込むことができた。
モジュールの読み込み処理とその規定方法を完全に理解するため、Dave Herman は Mozilla の Jason Orendorff と協力してモジュールローダーの参照実装 [Orendorff and Herman 2014] を JavaScript でプロトタイプした。2013 年 12 月、Herman [2013a] は Orendorff が書いた JavaScript コードを仕様の疑似コードで書き直す一度目の作業を完了させる。Allen Wirfs-Brock [2014a] は 2014 年 1 月にその疑似コードを参考的に ES6 ドラフトへ取り込んだ。Wirfs-Brock はモジュールローダーの非同期な性質が新たな複雑性を仕様に加え、おそらくは非決定性も加えることを発見した。この問題はローダー API を使うとユーザーが任意の JavaScript コードをモジュールの読み込み処理に追加できるという事実によって悪化する。2014 年の半ばには、非同期モジュール読み込みが追加する複雑性とローダー API の設計で絶え間なく発生する解決の難しい問題によって 2015 年内の ES6 リリースという目標の達成が危ぶまれるようになった。
シンプルモジュール提案の作成が始まったころ、Allen Wirfs-Brock [2010] はモジュールスコープの意味論とリンク処理はローダーパイプラインから分離できる可能性があることに気が付いた。ECMA-262 の以前の版は JavaScript ソースコードの構文と意味論を定義するものの、ソースコードへのアクセス方法に関しては何も触れていない。それは JavaScript エンジンをホストする環境の責任とされた。2014 年 9 月 の TC39 会合 [TC39 2014b] で Wirfs-Brock はモジュールでも同様のアプローチが可能だと主張した。ECMA-262 にモジュール読み込みパイプラインの仕様を含める必要はない、ということである。ECMA-262 でモジュールのソースコードが既に利用可能だと仮定すれば、規定する必要があるのは個別のモジュールの構文と意味論、そしてインポートされる束縛をエクスポートされる束縛にリンクする処理の意味論だけとなる。ブラウザといったホスト環境は非同期読み込みパイプラインを提供できるものの、その定義は仕様と切り離される。ローダーパイプラインの削除はローダー API の削除も意味する。TC39 はこの意見を受け入れたので、Wirfs-Brock はモジュールのほぼ完全な言語レベルの仕様を 2014 年 10 月の仕様ドラフトに取り込むことができた [Wirfs-Brock et al. 2014b]。モジュールの意味論とローダーパイプラインの分離によって、WHATWG は ECMAScript モジュールとウェブプラットフォームを繋ぎ合わせる仕様の策定に集中できるようになった。
21.3.5 アロー関数
ES2015 はアロー関数 (arrow functions) と呼ばれる関数定義式の簡単な記法を導入した。アロー関数は仮パラメータ、=>
トークン、関数本体の並びとして書かれる。この例を示す:
(a, b) => { return a+b }
パラメータが一つのときは括弧を省略でき、本体が一つの return
文からなるときは波括弧と return
キーワードを省略できる:
x => x /* 恒等関数 */
他の形式の関数定義と異なり、アロー関数は this
をはじめとした関数スコープの暗黙な束縛を再束縛しない。このためアロー関数は内部関数が外側の関数の暗黙な束縛への完全なアクセスを必要とする状況で有用になる。
アロー関数の主な動機は、プラットフォームあるいはライブラリの API 関数でコールバックが引数として使われるときに関数式を簡潔に書きたいという頻繁に寄せられていた要望だった。JavaScript 1.8 で Mozilla [2008b] は「式クロージャ (expression closure)」を実装していた14。この式クロージャは function
キーワードをそのまま使っており、本体には波括弧を持たない単一の式だけが許された。TC39 は function
を λ
, f
, \
, #
といった記号で置き換えた同様の簡潔な形式を議論したものの、コンセンサスに達することはなかった [Eich 2010b; TC39 Harmony 2010c]。
TC39 内には適切な末尾呼び出し語や Tennent [1981] の一致原則15をサポートする「ラムダ関数 (lambda function)」の提供への関心も存在した [Herman 2008]。ラムダ関数の支持者は言語およびライブラリが定義する制御抽象化の実装でラムダ関数は役立つと主張した。Harmony の取り組みが始まったころの es-discuss への投稿で、Brendan Eich [2008a] は Smalltalk のブロック構文に着想を得た簡潔なラムダ関数の構文を元々は Allen Wirfs-Brock による提案だとして紹介した。例えば {|a, b| a + b}
は Herman が提案したラムダにおける (a,b) {a + b}
と等価になる。Eich の投稿から es-discuss 上で簡潔な関数記法の様々な側面に関する非常に長い議論が始まったものの、結論は出なかった。ここでの主な収穫は構文のアイデアの多くがパースあるいは利便性に関して問題を抱えていること、そして JavaScript のローカルでない制御移動文 ── return
, break
, continue
── が制御抽象化を書くための仕組みを大きく複雑化していることの発見だった。
それから 30 か月にわたって大きな進展はなかったものの、2011 年 5 月に Brendan Eich [2011f; 2011g] が新たに二つのストローマン提案を二者択一のものとして執筆した。一つは「アロー関数」で、CoffeeScript が持つ同様の機能を参考にして作られた。この提案には関数を作るキーワードとして ->
と =>
の両方が含まれ、それぞれ構文および意味論が違っていた。また this
の値を変えるための特別な記法が存在した。もう一つの提案は Smalltalk と Ruby を参考にした「ブロックラムダ」で、こちらは Tennent の一致原則を守っていた。その後 9 か月の間、この二つの提案とその他の提案は es-discuss と TC39 会合で徹底的に議論された。アロー関数をパースできるようにする更新が既存の JavaScript 実装で簡単に行えるかどうかに関して懸念があった。問題はアロー関数の矢印記号が文の中間に書かれる上にパラメータリストが前に付くために、パラメータリストが括弧の付いた式として曖昧にパースされる可能性があることだった。ブロックラムダ提案に対しては、組み込みの構文的な制御構造を完全に取り込むと引数として受け取った関数に含まれる制御構造を適切にサポートできないという懸念 [Wirfs-Brock 2012a] があった。Brendan Eich はどちらかと言えばブロックラムダ提案を好んでいたものの、2012 年 3 月の TC39 会合が近づいたとき彼はアロー関数の方が TC39 に受理される可能性が高いと結論付けた。会合 [TC39 2012a] において彼はアロー関数の最終的な設計 [Eich 2012b] の基礎的な性質に関するコンセンサスの取れた決定を TC39 全体に説明した。
21.3.6 その他の機能
ここまでに議論していない主要な新しい言語機能には次のようなものがある:
- 計算されたプロパティ名や簡潔なメソッド構文といったオブジェクトリテラルの改善
- 宣言初期化子と代入演算子におけるオブジェクトと配列の分割代入
- 残余パラメータ、省略可能パラメータのデフォルト値、引数の分割代入といった仮パラメータの改善
- イテレータとジェネレータ ── Python に影響を受けているものの、大きく異なる
for-of
文、新しい構文と改良された構文におけるイテレータプロトコルの広い利用- 文字列と正規表現における完全な Unicode サポート
- 組み込みのドメイン固有言語をサポートするためのテンプレートリテラル
- プロパティキーとしての利用が意図されたシンボル値
- 2 進および 8 進の数値リテラル
-
適切な末尾呼び出し16
ライブラリの改善の例を示す:
- 新しい
Array
メソッド Array
などのコレクションオブジェクトに対するコンストラクタメソッドof
,from
の慣習- 型付きの配列クラス: 例えばバイナリデータを操作するための
DataView
とArrayBuffer
など; これらのクラスはどれもブラウザホストオブジェクトとして実装されていた Khronos Group [2011] による仕様をベースとしているが、言語の他の部分との統合が改善されている; 型付き配列はArray
メソッドの多くをサポートする - キー付きコレクション
Map
/Set
およびWeakMap
/WeakSet
Math
とNumber
に対する関数の追加- オブジェクトのプロパティをコピーするための
Object.assign
関数 - 非同期に計算される値に対する遅延アクセスのための
Promise
クラス - 内部のメタオブジェクトプロトコルを具象化するためのリフレクション関数
21.3.7 延期された機能と破棄された機能
ES6 策定の全体を通じて、TC39 によって検討されたものの最終的には ES2015 に取り込まれなかったストローマン提案が多くある。そういった提案の多くは最初のプレゼンテーションの後すぐに破棄が決定したものの、策定作業がかなり進んでいたものもあれば、Harmony 提案としては「受理」の状態に進んだにもかかわらず最終的なリリースには取り込まれなかったものもある。一度受理されてから切られた機能には破棄されたものと追加の作業のために延期されたものがあり、後者には将来の版で検討される可能性が残された。ES2015 が完成する前に切られた主要な機能と策定作業の例を示す:
内包表記 (comprehension) [Herman 2010a, d, Herman 2014a; TC39 2014a] は Python と JavaScript 1.7/1.8 に存在した同様の機能に基づき、初期化された配列を作成あるいはジェネレータ関数を定義するためのより簡潔で宣言的な手段を提供する予定だった。
モジュールローダー API [Herman 2013b] によって JavaScript プログラマはモジュールローダーが行う読み込み処理に対して動的に介入できるようになる予定だった。例えばモジュールの読み込み処理にトランスパイラのような機能を挿入できたり、モジュールの動的な定義がサポートできたりする。この API はモジュールローダーと共に延期された。
レルム API [Herman 2014b] によって JavaScript プログラマは新しいレルムの作成、設置、実行が可能になる予定だった。この機能はモジュールローダー API と密接に関係しており、さらなる設計作業のため延期された。
パターンマッチング [Herman 2011e; Rossberg 2013] は分割代入の一般化であり、Haskell に着想を得た反駁可能マッチング (refutable matching) を含む予定だった。
Object.observe
[Arvidsson 2015; Klein 2015; Weinstein 2012] は監視対象のオブジェクトのプロパティが改変されたときにイベントを生成する複雑なデータ束縛メカニズムを提供する予定だった。
並列 JavaScript (別名 River Trail) [Hudson 2012, 2014] は Intel と Mozilla による共同プロジェクトで、JavaScript プログラマがプロセッサの SIMD 機能を明示的に利用できるようになる予定だった。
値オブジェクト [Eich 2013] は Number や String と同様のプリミティブデータ型を定義するための演算子オーバーロードを含む一般化されたサポートを提供する予定だった。ライブラリから十進算術や任意長整数などを実装できるようになる可能性もあった。
ガード [Miller 2010c] は型に似た注釈で、動的な検証が可能になる予定だった。
21.4 Harmony トランスパイラ
Harmony の機能の策定とテスト、そしてコミュニティとのコミュニケーションにおいてトランスパイラは重要な役割を果たした。トランスパイラがあると規格の完成やブラウザでの実装を待たずに新しい機能をプロダクションで使えるようになる。JavaScript 開発者コミュニティによる ES2015 の素早い採用はトランスパイラがなければ不可能だった。Harmony を支えた重要なトランスパイラには次のようなものがある:
Narcissus [Eich et al. 2012] は JavaScript によってホストされる JavaScript エンジンであり、ES6 言語の実験のために Mozilla Research で使われた。
Traceur [Hallam and Russell 2011; Traceur Project 2011a] は Google によって開発されたトランスパイラであり、初期の ES6 機能を実験するために使われた。Traceur は忠実度の高い ES6 意味論の実装を提供したものの、その結果として実行時オーバーヘッドが大きかったためプロダクションでの利用に関する魅力はなかった。
Babel [2015] はオーストラリアの田舎に住んでいた当時 17 才の開発者 Sebastian McKenzie によって (最初は 6to5
という名前で) 開発された: 「2014 年 9 月 28 日、高校での試験に向けて勉強している間に書いた JavaScript ライブラリの最初のコミットを GitHub にプッシュした」 [McKenzie 2016] Babel はドラフト仕様への完全な意味論的準拠を犠牲にすることで実行時オーバーヘッドを最小化する。Babel は ES2015 の機能やその他の実験的な機能の早期アクセスを提供し、ES5 だけをサポートする古いブラウザやプラットフォーム上における ES2015 レベルの JavaScript コードの実行を可能にした。しかし Babel を利用する開発者の一部は実験的な機能や不正確な意味論、あるいは後に ECMAScript 規格に取り込まれた機能とは異なる機能に依存するコードを書くようになった。これによってネイティブ実装への移行が難しくなったり、一部の場合では TC39 による設計の柔軟性を制限するレガシーが生まれたりした。
TypeScript [Microsoft 2019] は Microsoft による無料の製品である。元々は ES5 に ES6 などの機能を付けたものをコンパイルターゲットとしていたが、後に ES2015 がターゲットとして追加された。TypeScript の最も重要な特徴は省略可能で静的解析可能な型システムと型注釈が存在し、それらを使って書かれたコードを常識的な動的型付けの JavaScript コードにコンパイルできることである。
プロダクションにおけるトランスパイラの利用、特に Babel と TypeScript の利用は多くの JavaScript 開発チームで起こった大きな文化的変革の一部だった。そういったチームで JavaScript は開発とデプロイのためのビルドツールチェーンを持った実行前にコンパイルされる伝統的な言語と同様に扱われ、プログラマが書いたオリジナルのソースコードを直接実行する動的実行環境とはみなされない。
21.5 ECMAScirpt 2015 の完成
2015 年 3 月の会合で TC39 [2015b] は当時の仕様候補 [Wirfs-Brock et al. 2015b, Wirfs-Brock et al. 2015c] を承認し、最終的な承認を受けるためにそれを Ecma 総会に提出した。Ecma 総会は 2015 年 6 月の会合で仕様を承認し、すぐに ECMA-262 第 6 版として公開した [Wirfs-Brock 2015a]。この仕様には「ECMAScript 2015 言語仕様 (ECMAScript 2015 Language Specification)」というタイトルがつけられた17。
ECMAScript 2015 の策定と公開には約 7 年の時間がかかり、数百人の人物が策定に貢献した。Harmony の取り組みが開始された 2008 年 7 月の会合から仕様候補が承認された 2015 年 3 月の会合までに TC39 会合は 41 回開催され、対面あるいはリモートで 145 人のメンバーが様々な頻度で参加した。ES2015 が策定される間に ES5/ES5.1, ECMA-402 国際化 API (The ECMAScript Internationalization API), ECMA-404 JSON データ交換フォーマット (JSON Data Interchange Format), そして Test262 検証テストスイートの策定も行われた。一部の参加者が最も関心を持っていたのはこういった取り組みだった。会合に参加した 145 人中 62 人は一つの会合だけに、たいていはオブザーバーとして参加した。
TC39 議長 John Neumann と Ecma 事務総長 István Sebestyén は会合がスムーズに進行するよう運営上のサポートを提供した。プロジェクト編集者 Allen Wirfs-Brock はプロジェクト全体を通して 38 個の仕様ドラフト [TC39 Harmony 2015] をリリースした。図 47 に示す 7 名は事実上プロジェクトの最初から最後まで関わった技術貢献者である。加えて図 48 に示す 35 名は会合に 5 回から 24 回参加し、多くはプロジェクトに大きく貢献した。ES2015 が策定される間に JavaScript 開発者コミュニティのメンバー数百人が es-discuss メーリングリストに 36,000 通を超えるメッセージを投稿した [TC39 et al. 2006]。TC39 のバグ追跡システムには 4,000 個を超える ES2015 仕様ドラフト関連のチケットが開かれた。
Allen Wirfs-Brock (プロジェクト編集者) | Microsoft, Mozilla |
Brendan Eich | Mozilla, 招待された専門家 |
Mark S. Miller | |
Waldemar Horwat | |
Dave Herman | Mozilla, ノースイースタン大学 |
Douglas Crockford | Yahoo!, PayPal |
Erik Arvidsson |
Sam Tobin-Hochstadt (24) | Andreas Rossberg (13) | Rafael Weinstein (10) | Chris Pine (7) |
Alex Russell (21) | Oliver Hunt (12) | Jeff Dyer (8) | Mike Samuel (6) |
Luke Hoban (20) | Norbert Lindenberg (12) | David Fugate (8) | Ihab Awad (5) |
Cormac Flanagan (18) | Sam Ruby (12) | Domenic Denicola (7) | Reid Burke (5) |
Yehuda Katz (17) | Brian Terlson (12) | Rick Hudson (7) | Andreas Gal (5) |
Rick Waldron (17) | Sebastian Markbage (11) | Jafar Husain (7) | Peter Jensen (5) |
Eric Ferraiuolo (15) | Jeff Morrison (11) | Dimitry Lomov (7) | Pratap Lakshman (5) |
Tom Van Cutsem (14) | Rob Sayre (10) | Ben Newman (7) | Nicholas Malsakic (5) |
Nebojsa Ćirić (13) | Matt Sweeney (10) | Caridy Patino (7) |
ES6 の策定が進む中で TC39 への関心と参加者は劇的に増加し、策定が完了した後も衰えることはなかった。Harmony に関する議論が初めて行われた 2008 年 7 月の TC39 会合の参加者は 8 組織を代表する 13 名だったのに対して、ES2015 が完成してから 1 か月後に開かれた 2015 年 7 月の会合には 15 の組織を代表する 34 名が (一部はリモートで) 参加した。2019 年 7 月の TC39 会合には 24 の組織を代表する 76 名が (46 名が対面、30 名がリモートで) 参加している。
21.5.1 ポスト ES6 の将来を見据えた準備
ES6 策定が完了に近づいていた 2013 年と 2014 年、TC39 は将来の版の策定をどのように進めるべきかについて検討を始めた。ES6 のプロセスが抱えていた問題の一つが、ECMAScript 規格として公開される数年前に設計が完成する機能が存在することだった。この事実は主要なブラウザのほとんどが採用していた「エバーグリーンブラウザ」の考え方と対立していた。エバーグリーンブラウザは数週間に一度のペースで更新され、バグ修正や新機能は可能な限り早く利用可能にできるようになっている。TC39 メンバーの多くはブラウザの高速な進化と歩調を合わせる、これまでより格段に高速な更新サイクルがECMAScript 規格にも必要だと感じていた。
一年ごとのリリースサイクルが提案された。そうすれば個別の新機能を規格に素早く反映できるようになる。また一年ごとにリリースを行えば仕様のバグをすぐに修正できるので、何年にもわたって長い正誤表を管理する必要もなくなる。一年という更新サイクルは標準化組織の基準からすると極端に短かったが、Ecma はこのスケジュールの採用に合意した。
規格の更新が一年ごとになったために、TC39 で行われる新しい言語機能の策定をより細かく管理する必要が生じた。一部の設計の取り組みは完了までに数年を要すると予想されていたので、複数のリリースをまたぐ機能策定プロジェクトを扱えて、さらに互いに重なり合う異なる機能策定サイクルを調整できるプロセスが必要とされた。また ES6 では仕様の執筆が一人の編集者に依存しすぎていたという懸念もあった。一年ごとのリリースを可能にするため、チャンピオンは自身の機能に関する仕様執筆のほとんどを行わなければならないことになった。
Rafael Weinstein と Dimitry Lomov は新しい機能提案を 5 つのステージを通して洗練させる策定プロセスを提案した [TC39 2013c; Weinstein and Lomov 2013]。Weinstein は Allen Wirfs-Brock と協力してプロセスの定義と文書化を進めた [Weinstein and Wirfs-Brock 2013]。補遺 P に新しいプロセスと策定ステージの説明がある。2014 年から TC39 は ES6 より後の全ての取り組みに対してこのプロセスを採用した。2020 年 6 月の本稿執筆時点で、TC39 は一度も失敗することなく毎年 6 月に ECMAScript 仕様の新しい版を公開している。
-
訳注: 「ストローマン (strawman)」は「たたき台 (となるアイデア)」のこと。 ↩︎
-
Herman はチャンピオンモデルというアイデアをどこで見つけたかを覚えていないものの、Oscar Nierstrasz [2000] が学術会議における論文選定のために提唱したチャンピオンパターンに関連している可能性が高い。 ↩︎
-
訳注: この「チャンピオン (champion)」は「支持者」あるいは「擁護者」のことであり、「(競争の) 勝者」のことではない。 ↩︎
-
「夢」のラベルと説明は Brendan Eich のブログ記事にある文章を短く言い換えたもので、「夢」のコードスニペットは直接引用したものである。彼がセミコロンを使わないスタイルを使って「夢」のコードを書いていることに注目してほしい。 ↩︎
-
基礎トラップはプリミティブである。派生トラップのデフォルトの振る舞いは基礎トラップを使って定義される。 ↩︎
-
ES5 はユーザー定義の内部メソッドをサポートしないので、この不変条件を明示的に強制する必要がなかった。 ↩︎
-
プロキシトラップのたびに不変条件を強制する処理のコストは設計上の懸念だった。このオーバーヘッドを削減するための代替設計として通知プロキシ (Notification Proxy) [Van Cutsem 2013] が短い間検討された。 ↩︎
-
「高整合性オブジェクト (high-integrity object)」は頑強な情報隠蔽を支援する。高整合性オブジェクトの構造、継承関係、カプセル化された状態、メソッドはオブジェクトを定義するコード以外からは直接改変または拡張できない。 ↩︎
-
例えばメソッドの非列挙性や読み込み専用プロパティの設定など。 ↩︎
-
ES6 より前の版において、仕様の第 15 節は組み込みオブジェクトとクラスを定義する。 ↩︎
-
Russell Leggett は受験における「滑り止め (safety school)」の比喩を使っている。志望する学校に一つも受からなかったときの保険として出願する学校を「滑り止め」と呼ぶ。 ↩︎
-
コンストラクタメソッドは結局
static
キーワードを使うものとして設計に戻された。 ↩︎ -
典型的な CommonJS モジュールは名前空間として使われる単一のオブジェクトをエクスポートする。 ↩︎
-
この実装はとある ES42 提案 [TC39 ES4 2006c] に基づいている。 ↩︎
-
関数に含まれるコードの一部分を関数に包んですぐに呼び出した場合、その関数呼び出しはコードを関数に包まず直接実行するのと同じ効果を持つべきである、という考え方。 ↩︎
-
適切な末尾呼び出し (proper tail call, PTC) に関しては論争が尽きない。少なくとも一つの主要ブラウザは PTC を正しく実装しているのに対して、他のブラウザはサポートを拒否している。 ↩︎
-
TC39 は第 6 版以降の更新を一年に一度のペースで行う計画を立てていたので、仕様のタイトルに年数が追加された。JavaScript コミュニティが改訂された仕様を版数ではなく年数で呼ぶようになることが期待された。 ↩︎