20. ES3.1 (ES5) の策定
2007 年のほぼ全体を通して、ES42 ワーキンググループは ES3.1 の活動が ES42 を脱線させるための敵対的行為に過ぎず、技術的重要性は何もないと考えていた。しかし Douglas Crockford, Pratap Lakshman, Allen Wirfs-Brock は ES3 仕様に最新の情報を反映させて相互運用性の問題の原因を修正するためのインクリメンタルな改善に取り組んでいた。彼らは初期目標の提示と機能レベルの変更の提案 [Lakshman et al. 2007] を行った後、最初のステップとして当時のブラウザにおける JavaScript の状況、そして Web Reality が ES3 仕様からどの程度離れているかを詳しく調査した。
ES3.1 ワーキンググループがまず対処しなければならないと感じていたのが、Microsoft の Internet Explorer に含まれる JScript 実装が Web の規格に準拠していないという評判だった。ECMAScript に関連するこの懸念がどれほどの正当性と範囲を持つかを理解するために、Allen Wirfs-Brock は IE JScript が ES3 に従っていない部分を全て特定する調査を Pratap Kalshman に依頼した。この調査の結果として JScript Deviations from ES3 (JScript が持つ ES3 からの逸脱) と題された 87 ページの報告書 [Lakshman 2007c] が 2007 年 9 月に完成した。この報告書には主要な部が三つある。一つ目の部では当時の JScript 実装が ES3 仕様の明確な要件から逸脱している個所が一つずつ説明され、逸脱のそれぞれについて対応する ES3 仕様の文言、逸脱を確認できるテストケース、そして当時の Internet Explorer, Mozilla Firefox, Opera, Apple Safari のリリースでテストを実行したときの結果が示される。これらは当時「トップ 4」とみなされていたブラウザである。図 29 に発見された逸脱の例を示す。説明される逸脱には Internet Explorer に特有なものもあれば、テストされた全てのブラウザに共通したもの、あるいは Internet Explorer の他に一つまたは二つのブラウザに共通したものもあった。
2.15 String.prototype.split: §15.4.4.14
ES3 には「もし分離文字が正規表現で、キャプチャを行う括弧の組を含むなら、分離文字が入力とマッチするたびにキャプチャを行う括弧の組の結果 (undefined を含む) が出力配列に挿入される」とある。
JScript はキャプチャを行う括弧の組を無視している。FF は undefined ではなく空文字列を出力する。
例:
<script> alert("A<B>bold</B>and<CODE>coded</CODE>".split(/<(\/)?([^<>]+)>/)); </script>
出力:
IE:
A
,bold
,and
,coded
FF:
A
, ,B
,bold
,/
,B
,and
, ,CODE
,coded
,/
,CODE
,Opera: FF と同様
Safari: IE と同様
JScript Deviations Report の第 2 部は ES3 の仕様が明示的に実装依存と定義している個所、あるいは定義が不適切な箇所を全て特定している。この部にもテストケースと四つの主要なブラウザでの実行結果が載っている。最後の部は Internet Explorer が実装する機能の中で ES3 仕様に対する言語拡張であるもの説明している。これとは別に Wirfs-Brock [2007b] は Firefox が実装する ES3 仕様に対する言語拡張をまとめたリストを用意した。Douglas Crockford と Allen Wirfs-Brock は 2007 年 8 月 16 日に顔を合わせ、これらの文書のドラフトに対するレビューを行った。この作業の結果として ES3.1 仕様で行う変更の試案 [Wirfs-Brock and Crockford 2007] が完成した。
ES3.1 の策定作業は 2008 年 1 月の TC39 会合で本格的に始動した。この会合では ES3.1 の目標がレビューを受け、TC39 参加者の何人かが ES3.1 に取り組むことへの関心を表明した。2 月 11 日、Lakshman は TC39 のプライベートメーリングリストに ES3.1 への呼びかけを投稿する。このメールは昨年の夏にまとめられた仕様からの逸脱と相互運用性の問題をまとめた文書に注意を促し、さらなるフィードバックを求めた。2 月 21 日には通話会議が開かれ、週二回の通話会議を開くスケジュールが確立された。これらの通話会議の参加者は以前の ES3.1 の議論より大幅に増加した。図 30 に定期的な参加者を示す。内部ではメールを直接送り合うことで提案のやり取りや議論が行われた。ES3.1 に関する議論は es4-discuss メールフォーラムでも行われたものの、ES42 に関連する投稿が非常に多かったために ES3.1 だけに関する話題を見つけるのが難しくなった。そこで 4 月に es3.1-discuss メールフォーラムが作成 [TC39 et al. 2008] され1、会合外での ES3.1 の設計議論はそこで行われるようになった。
Douglas Crockford | (Yahoo!) |
Pratap Lakshman | (Microsoft) |
Mark S. Miller | (Google) |
Adam Peller | (IBM) |
Sam Ruby | (IBM) |
Allen Wirfs-Brock | (Microsoft) |
Kris Zyp | (The Dojo Foundation) |
初期に議論されたトピックの一つ [TC39-2008d] は ES3.1 の全体的な目標および問題の解決と新機能の追加で基準となる設計指針のレビューだった。初めのころ Microsoft Live チームの開発者やいくつかのウェブフレームワーク開発者は、これまでのバージョンのブラウザでパースが失敗するような新しい構文的拡張は全て避けるべきだという姿勢を提唱した。しかし、この「構文拡張禁止」のルールはあまりにも制限的であり、様々なブラウザが何らかの構文的な拡張を既に持っているという現実を無視していた。この議論から「4 分の 3」ルールが生まれた。これは Microsoft の JScript が仕様から逸脱している点をまとめた文書で調査された最もよく使われる 4 つのブラウザ (Internet Explorer, Firefox, Opera, Safari) のうち 3 つで機能あるいは振る舞いが共通するなら、それを ES3.1 仕様に採用すべきとするルールである。このルールはブラウザの相互運用性の問題に対して ES3.1 が取るべきアプローチに関するより広い議論につながった。
ES3.1 の最優先の指針を「主要なブラウザで相互運用を達成している既存のウェブページの振る舞いを変化させる言語変更を取り入れて『ウェブを壊す』のを避ける」とすべきであることに合意があった。ただ既存のウェブページは数億も存在する。ECMAScript 仕様のどの部分が確かなものとみなせるのだろうか? どの変更がウェブを壊すのだろうか? ブラウザ実装者からの事例報告によると、存在するウェブページの数が膨大なために、相互運用性のあるブラウザ機能は (どれだけ仕様が漠然としていようと、どれだけ奇妙な使い方であろうと) 全て何らかのページによって利用されているだろうとのことだった。この報告に基づけば、主要な 4 ブラウザに共通する機能は変更できず、4 ブラウザ中 3 つに共通する機能は標準化の有力な候補となる。しかし機能や振る舞いが 4 ブラウザ中 2 つに共通していたり、それぞれのブラウザで異なっていたりしたらどうすればよいだろうか? そういった機能や振る舞いは既存のウェブの相互運用性にとっておそらく重要でないので、標準化プロセスで変更しても構わないだろうとされた。
ES3.1 ワーキンググループは ECMAScript 仕様に実装の差異を生む可能性のある部分があるとウェブページの相互運用性に傷がつくことも認識した。伝統的な言語の仕様では言語実装者に柔軟性を提供するため、あるいは既知の実装間の差異を受け入れるために実装依存の差異を許すこともあるかもしれないが、これは独立して実装される複数のウェブブラウザからアクセスできる世界規模の相互運用可能なウェブという考え方と根本的に両立しない。ECMAScript 仕様は伝統的な言語仕様より規範的かつ詳細に書かれなければならず、可能な場合は必ず既存の仕様に存在する実装の差異を生む可能性のある部分を無くすようにしなければならない。2 月に行われたこういった初期の議論の後 Douglas Crockford [2008a] は改訂された ES3.1 の目標を TC39 Wiki に投稿した (図 31)。
- ブラウザの実装の統一: 4 つのブラウザブランド中 3 つで既に実装されている機能、あるいはユーザーのコンピューターの 4 分の 3 に存在する機能の採用を検討し、ブラウザ間での非互換性を削減する。
- ES3.1 では分かりにくい構文や問題のある構文を削除することで、カジュアルな開発者にとって便利なように言語を改善する。
- ES3.1 では分かりにくい構文や問題のある構文を削除することで、主要なウェブサイトにとって便利なように言語を改善する。
- ES4 は ES3.1 の上位集合となる。
- ES3.1 はセキュアな部分集合を構築するためのフレンドリーな基礎となる。
- ES3.1 は ES3 に存在する誤りの修正を試みる。
- ES3.1 では新機能に具体的なデモを要求する。
- ES3.1 は性能、セキュリティ、信頼性の観点で問題のある機能を非推奨化 (あるいはオプトインとして削除) する可能性がある。
- ES3.1 は仮想化機能を提供し、ホストオブジェクトのエミュレートを可能にする。
2008 年 3 月の対面会合で ES3.1 ワーキンググループは実際の ES3.1 仕様文書をすぐに書き始めるのが重要だということに合意した。Pratap Lakshman は Mozilla が管理する ES3 正誤表 [Horwat 2003b] にある修正を施したバージョンの ES3 仕様を会合に持参しており、ワーキンググループはそれを ES3.1 の基礎文書として使うことに合意した。これまでの版と同じように、仕様文書は Microsoft Word を使って執筆された。また、変更のレビューを受けるため、そして ES42 の取り組みに変更を統合できるようにするために、第 3 版への変更を追跡することになった。ワーキンググループのメンバーにはそれぞれ特定の新機能に対する仕様文書の執筆が割り当てられ (図 32)、メンバーが執筆を完了させると Lakshman がマスターのドラフトにまとめていった。
Lakshman | Mozilla の「Array Extras」および reduce , reduceRight をベースとした新しい Array メソッド |
Lakshman | 文字列に対する添え字を使った配列風アクセスのサポート |
Lakshman | Date の改善 |
Lakshman | strict モードにおけるプロパティアクセスの意味論 |
Crockford | JSON サポート |
Crockford | Unicode サポート |
Peller | JScript の逸脱をまとめた文書に基づいて推奨される変更をまとめる |
Ruby | 十進算術 |
Zyp | オブジェクトリテラルにおけるゲッターとセッターの構文 |
Wirfs-Brock | プロパティの作成と検証を行う静的メソッド |
Wirfs-Brock | 疑似コードの記法と慣習を改善する |
Miller | オブジェクトの freeze /seal およびセキュリティの観点からの全体レビュー |
2008 年 5 月 29 日、Pratap Kalshman は TC39 Wiki に ES3.1 仕様の初期ドラフトを投稿する。その後ほぼ毎週ドラフトの更新版が投稿され、予定された会合の二週間から三週間前には「レビュードラフト」が投稿された。2008 年 5 月 29 日から 2009 年 3 月 2 日までの間に、合わせて 26 個の中間ドラフトが投稿された。
IBM は JavaScript に十進算術が必要だと長い間主張していた。1998 年 11 月の TC39 ワーキンググループ会合から始まり、Mike Cowlishaw は ES3 と ES41 で十進算術を採用するよう呼びかけていた。IBM が TC39 に再び参加し ES42 と ES3.1 に貢献を始めると、IBM の代表者はここでも十進算術の採用を強く主張した。これを受けて他の TC39 のメンバーは、十進算術のサポートを含まない新しい言語規格にはとにかく反対することが IBM のポリシーなのだろうと確信した。メンバーの多くは十進算術という機能の実現可能性について懐疑的だったものの、Brendan Eich は IBM に同情的であり、Firefox に報告されるバグで最も多いのは二進浮動小数点数演算の意味論を理解していない JavaScript 開発者からのものだと指摘した。Eich は Sam Ruby に手を貸し、IEEE 754-2008 の十進浮動小数点数を新しいプリミティブ型として持ち、従来の数値型と組み合せたときでも混合モードの表現で計算できる Mozilla の SpiderMonkey エンジンをベースにしたプロトタイプの実装の立ち上げを支援した。この十進算術の機能のほぼ完全な仕様は 2008 年 9 月および 2008 年 11 月の ES3.1 ドラフトに取り込まれた。2008 年 11 月 19–20 日の会合では ES3.1 ドラフトに残すべき機能と削除すべき機能の最終的な決定が主な議題となり、十進算術のサポートが最初に検討された。この会合での TC39 の結論は、十進算術の設計は未熟であり、ES3.1 を遅らせない限り解決できないであろう未解決の問題が残っているというものだった。この懸念は会合の議事録 [TC39 2008a] に記録されており、次の結論が記されている:
こういった懸念があるので、十進算術のサポートは ECMAScript の Harmony 改訂まで遅らせることに決定した。出席者は十進算術の ECMAScript 提案を策定する中で非常に大きな進歩があったことを認め、この作成に対して注いだ労力について IBM の Sam Ruby に謝意を表明したい。出席者は Sam および他の TC39 メンバーが提案の作成を続けることを奨励し、完全に統合された一般的なバージョンの十進算術が Harmony 改訂の中心部分になるだろうと楽観的に考えている。
十進算術に関連する文章は 2009 年 2 月公開の次のレビュードラフトから姿を消した。
2009 年 3 月 25–26 日の会合 [TC39 2009d] で Pratap Lakshman は ECMA-262 編集者を退任することを発表した。Microsoft がレドモンドを拠点とする新しいグループに JavaScript 開発の担当を移すことになり、Lakshman が一緒にレドモンドに移る提案を断ったためである。TC39 は Allen Wirfs-Brock を後任の編集者に指名した。Wirfs-Brock は TC39 会合の休憩時間に Brendan Eich に近づいて、ES3.1 を小数点付きの数字ではなく完全な整数を持つ版として命名し直してはどうかと提案したことを覚えている。新しい整数を持たせるべきと Wirfs-Brock が考えたのは、ES3.1 が過去の三つの版と同程度の重要性を持つ ECMA-262 の本格的な改定になってきていたからだった。ES4 が中止されたことは TC39 外部にも広く知られていたので、ES3.1 を「第 4 版」と命名すると JavaScript 開発コミュニティと Web 検索エンジンの両方に混乱を招くだろうと思われた。その代わり Ecma は ECMA-262 の「第 4 版」という名前を完全に捨て、ES3.1 の取り組みを「第 5 版」としてリリースすべきだと Wirfs-Brock は提案した。Eich はこの考えに同調した。会合が再開すると Eich はこの考えを TC39 全体に伝え、TC39 はそれを了承した。また TC39 は当時最新のドラフトにこの会合で合意があった更新個所を取り入れたものを最終ドラフトとすることに合意した。第 5 版と命名された「最終ドラフト」は 2009 年 4 月 7 日に公開された [Lakshman et al. 2009]。この最終ドラフトの後には技術上および編集上の細かな修正を施したリリース候補ドラフトが続いた。2009 年の 8 月、引数オブジェクトが Array.prototype
を継承するようにした変更が Prototype.js というフレームワークに予期しない影響を与え、Apple のウェブサイトのいくつかと NASA のウェブサイトが壊れてしまうことが Apple によって発見された [Hunt 2009]。この変更は最終仕様から削除された。
2009 年 9 月、TC39 [2009b] は ES5 の完了を投票で受理し、承認のため Ecma 総会に提出した。Ecma 総会でレビューと承認を受けるための最終ドラフトは 2009 年 10 月 28 日に投稿され、ECMA-262 第 5 版は 2009 年 12 月 3 日の Ecma 総会 [Ecma International 2009a] で承認された。第 3 版の承認から実に 10 年後のことである。総会での投票結果は 19 票の賛成と 2 票の反対だった。IBM は十進算術のサポートが規格に含まれないために反対し、Intel は知的財産権に関して仕様をレビューする十分な時間が取れなかったというだけの理由で反対した。
ECMA-262 第 5 版は ISO/IEC における ECMAScript 規格のファストトラック改訂として提出された。提出された仕様は ISO 国際団体によるレビューを受け、そこからのフィードバックを受けて Allen Wirfs-Brock は様々な編集上の修正や明確化を仕様に取り入れた。改訂された仕様は 2011 年 6 月に ECMA-262 第 5.1 版および ISO/IEC 16262 第 3 版として公開された。
20.1 ES5 の技術設計
ES3.1 が最初に掲げた目標は非常に控えめだったものの、ES5 には技術的イノベーションがいくつか含まれている。
20.1.1 strict モード
ES5 の strict モードは JavaScript の「ミスや不便な点を修正する」という Douglas Crockford が掲げた目標に向けた直接的な方策と言える。不便な点の一部は「オブジェクトリテラルの内側やドットの後に付くプロパティキーに予約語を使えない」のように当時の仕様でパースエラーを生じさせるので、既存のコードに影響を与えることなく ES5 で修正できた。しかし JavaScript の欠陥の多くは直すと既存のコードの振る舞いが変化して「ウェブが壊れる」ので、無条件には修正できなかった。strict (厳格) モードのアイデアは、そういった修正を取り入れた JavaScript の方言を新しいコードや更新されたコードで使う選択肢を明示的なオプトインを通して JavaScript 開発者に与えるというものである。ブラウザは strict モードのコードとレガシーな non-strict モードのコードを両方サポートする必要が生じる。理想的には strict モードを個別の関数レベルで選択できるようにして、既存のスクリプトを少しずつ strict モードを使うように変更していけるのが望ましいとされた。
いずれ新しく書かれるコードでは strict モードが主流になることが期待されたものの、そのためには最初に採用されるかどうかが問題だった。ES5 の strict モードが全ての主要なブラウザで実装されるまでにはかなりの時間を要する可能性があると思われていた。ブラウザゲーム理論によれば、strict モードを選択すると他の有名なブラウザでそのスクリプトが動かなくなるなら、開発者は strict モードを使わない。この問題は strict モードを減算的にすることで回避された。つまり strict モードは ECMAScript に新しい機能を追加せず、問題のある ECMAScript の機能の削除だけを行う。こうしておけば、strict モードで書かれたバグの無いコードは strict mode をサポートしないブラウザでも開発者の意図通りに動作する。
初期の問題の一つは strict モードへのオプトインをどう行うかだった。strict モードを細かく選択するには、スクリプトの中に簡単に埋め込めるメカニズムを通したオプトインが必要となる。例えば <script>
要素の属性などを使って外側から指定するようにはできない。ES42 では ECMAScript コードの中にディレクティブを入れることで異なるモードを選択する方法が検討されたことがあった。しかし新しいディレクティブを追加するのは ES3.1 の「新しい構文を追加しない」という設計指針に反する。一つの可能性として、特別な形式のコメントをディレクティブとして使うという案があった。しかし JavaScript ミニマイザはコメントを削除するので、コメントに意味論的な意味を持たせるべきではないと ES3.1 ワーキンググループは感じていた。
Allen Wirfs-Brock は ECMAScript が式文 (Expression Statement) の構文を持つために、任意の式が正当な文とみなされることに気付いた。セミコロンが明示的あるいは (ASI を通して) 暗黙に最後に付いてさえいれば、文字列リテラルさえも正当な文となる。これは "use strict";
のような文が構文的に正当な ES3 コードであることを意味する。もちろん文字列リテラルは定数なので、ES3 でそれを評価しても何も起こらない。つまり文字列リテラルは noop語 である。そういった文を strict モードへのオプトインに利用し、ES5 コードを読み込んだ ES3 実装にはそのオプトインを無視させるというのは非常に安全なように思えた。ワーキンググループはこのアイデアを採用した。"use strict";
という形をした文がスクリプトあるいは関数本体の最初にある場合、それはスクリプトあるいは関数の全体が strict モードの意味論で処理されるべきであることを意味する。
strict モードの主な目標の一つは、書くのは簡単にもかかわらず実行時に検出するのが難しいコーディングエラーを明示的に捕捉することだった。strict モードでは次の操作が新しく実行時エラーとなる:
- 未定義の識別子に対する代入。レガシーの JavaScript では変数名をタイプミスするとグローバルオブジェクトに新しいプロパティが作成される。
- 読み込み専用の固有プロパティあるいは継承プロパティに対する代入。レガシーの JavaScript では何も起きず、警告も出ない。
- 拡張可能でないオブジェクトに対するプロパティの作成。拡張可能でないオブジェクトは ES5 より前に存在しないものの、レガシーの JavaScript との一貫性のため strict モードでない ES5 ではそういったオブジェクトに対してプロパティを作成しても何も起こらず、警告も出ない。
- 削除不可能なプロパティに対する
delete
演算子の適用。レガシーの JavaScript ではfalse
が返る。 - 変数参照に対する
delete
演算子の適用 (構文エラーとなる)。レガシーの JavaScript において明示的に宣言された変数に対するdelete
はfalse
を返す。変数参照がwith
文を通してオブジェクトの後ろ盾を持つ場合、およびグローバルオブジェクトのプロパティである場合、レガシーの JavaScript では削除が起きる。
strict モードはプログラムを分かりにくくする機能、プログラムの最適化を難しくする機能、そして使うとプログラムがセキュアでなくなる機能の削除および改変も行う:
with
文は無効化される。変数参照の動的スコープの一種を提供するwith
文は分かりにくく、実装による最適化が難しい。eval
関数を使って現在のスコープに新しい束縛を動的に追加することはできない。eval
とarguments
は変数とパラメータの名前として使えない。- 関数の引数オブジェクト (§3.7.5) は仮パラメータに結び付かない。その代わり、strict モードの引数オブジェクトは関数に渡された時点における引数の値のスナップショットを要素に持つ配列風オブジェクトとなる。この要素を改変しても対応する仮パラメータの値は変化せず、逆も同様に変化しない。
-
strict モードの関数の引数オブジェクトは
callee
プロパティ (§5) を持たない。引数オブジェクトを他のコードに渡しても、元の関数の呼び出しが暗黙の内に可能になったりはしない2。 - 実装は strict モードの関数の引数オブジェクトに
caller
プロパティ (§3.7.5) を持たせてはいけない。caller
プロパティは非標準だったにもかかわらず ES3 の言語拡張として広く実装され、関数のコールスタックを走査したり呼び出した関数を取得したりできるようになっていた。 - strict モードの関数を
this
が特定されない形で呼んだとしても、グローバルオブジェクトが関数から利用可能になることはない (§3.7.4)。
Douglas Crockford [2007d] が作成した ES3 のミスと不便な点のリストに載っている他の機能も strict モードに含めるべきかどうかが検討されたものの、含めないことになった。各機能について、その機能は望ましくないというコンセンサスが TC39 で得られなかったか、その機能を修正すると変更が減算的にならないことが分かったかのいずれかである。例えば Crockford と多くのメンバーは JavaScript の自動的なセミコロンの挿入を好ましく思わなかったものの、明示的なセミコロンを持たないコードを好む開発者も多くいた。あるいは typeof null
が "object"
でない値を返すようにする変更は減算的にならない。
20.1.2 ゲッター、セッター、オブジェクトメタ操作
JavaScript では最初の実装から、組み込みオブジェクトやホスト提供オブジェクトの一部のプロパティが特別な特性を持ち、その特性は JavaScript コードを使って作成されたオブジェクトからは利用できなかった。例えば一部のプロパティの値は読み込み専用で、delete
演算子で削除できないプロパティも存在した。また、組み込みオブジェクトおよびホストオブジェクトのメソッドプロパティは for-in
文でプロパティを列挙するときスキップされる。ES1 において、これらの特別な意味論はそれぞれ ReadOnly, DontDelete, DontEnum という属性を仕様で使われるオブジェクトプロパティのモデルに関連付けることで規定され、これらの属性は言語機能の意味論を定義する疑似コードで利用される。ただしこういった属性は具象化されない ── JavaScript コードから新しいプロパティあるいは既存のプロパティの属性の変更を可能にする言語機能は存在しない。ES3 では DontEnum 属性の有無を判定する Object.prototype.propertyIsEnumerable
メソッドが追加されたものの、ReadOnly 属性と DontDelete 属性に対応する破壊的でない判定は存在しなかった。同様にブラウザの DOM が提供するホストオブジェクトの多くは「ゲッタープロパティ」および「セッタープロパティ」と呼ばれる (ES5 では「アクセッサプロパティ」と命名された) プロパティを公開し、このプロパティの値を設定あるいは取得すると特定の処理が行われる。こういった機能に対する標準化されたサポートが存在しないので、JavaScript プログラマは組み込みのオブジェクトやホストオブジェクトと同じ慣習に従うライブラリやそういったオブジェクトを忠実にエミュレートする polyfill を定義できなかった。
これらの問題に対する統一された解決策は ES5 の新機能の中で最も大きな機能集合である。この機能集合に公式の名前は無いものの、非公式には「静的オブジェクト関数3」や「オブジェクトリフレクション関数」などと呼ばれる。Allen Wirfs-Brock [2008] はこの機能集合について設計の理論的根拠を示した文書を作成した。この文書にはいくつかの使用例と共に規範とされた設計指針が示されている:
- メタのレイヤーとアプリケーションのレイヤーを綺麗に分ける。
- メソッドの個数やメソッドの引数の複雑さなど、API の表層的な規模の最小化を試みる。
- 命名とパラメータの設計において利便性に焦点を当てる。
-
設計の基礎的な要素の適用を繰り返し試みる4。
- もし可能なら、プログラマと実装者が API の使用を静的に最適化できるようにする。
一つ目の指針は Object.prototype
に propertyIsEnumerable
のようなメソッドを追加するのを阻止する。こういったメソッドはメタのレイヤーとアプリケーションのレイヤーの境界をさらにぼやけさせるためである。その代わり、ES5 ワーキンググループはそういった関数を何らかの名前空間オブジェクトのプロパティとすることでアプリケーションオブジェクトから分離させることを決定した。ワーキンググループはこの名前空間オブジェクトとして Reflect
と呼ばれる新しい組み込みのグローバルオブジェクトを導入することを検討したが、既存のコードとの名前の衝突が問題になった。最終的には、Object.prototype
のプロパティではなく Object
コンストラクタのプロパティとして新しい関数を公開することが決定された。Object
コンストラクタが名前空間として有力な候補になったのは、これが当時の実装およびそれまでの規格においてプロパティが規定されていない既存のグローバル変数であったためである。また Object
という名前はオブジェクトの定義の細かな性質を操作するというリフレクションメソッドの特徴とも矛盾しない。
次の問題は API の形態の決定だった。二つ目の指針に従い、プロパティ属性を取得・設定する関数を属性ごとに分けたり、アクセッサプロパティで実行される関数を設定・取得する関数をプロパティごとに分けたりするのを ES5 設計者は避けようとした。この機能を少数の関数に統合するための様々な方法が検討された。例えば「読み込み専用」といった真偽値の属性をビットでエンコードして単一の関数で操作する方法や、たくさんの位置引数を持った単一の関数を用意する方法が選択肢に挙がった。しかしこの二つのアプローチは利便性に劣る。省略可能なキーワード引数を使えば利便性の問題が解決できると思われたものの、ES5 にはキーワード引数が存在しなかった。
Allen Wirfs-Brock は様々なプロパティ属性に対応するプロパティを持つ記述子 (descriptor) オブジェクトを使うことを提案した。この記述子はプロパティ属性と取得の両方に利用できる。Wirfs-Brock による第 1 ドラフト提案5には obj
というオブジェクトにプロパティを加える API として考えられる選択肢の一つが示されている:
Object.addProperty(obj, {
name: "pi",
value: 3.14159,
writable: false
});
この例では記述子がオブジェクトリテラルとしてコーディングされ、記述子に含まれないプロパティに対応するプロパティ属性に対してはデフォルト値が使われる。仮定的な defineProperty
関数は同様の記述子を受け取り既存のプロパティの属性の値を変更する関数とされた。defineProperty
では記述子のプロパティで指定されない属性は変更されずにそのままとなる。最後に、getProperty
関数はオブジェクトが持つプロパティの完全な記述子を取得するのに利用できた。
Mark Miller はこの提案を改善し、defineProperty
が「新しいプロパティの追加」と「既存のプロパティの改変」を両方サポートできるように定義することを提案した。加えて Miller は記述子の name
プロパティは不要であり、対象のオブジェクトの影響を受けるプロパティの名前をプロパティキーに取るオブジェクトで記述子を包めば name
を削除できるとも指摘した。このように「プロパティマップ」を使うと、一度の呼び出しで複数のプロパティを定義することもできるようになる。例えば次の操作は x
と y
という二つのプロパティを定義する:
Object.defineProperties(obj, {
x: {value: 0, writable: true},
y: {value: 0, writable: true}
});
Miller は defineProperties
が単一のプロパティを定義するのにも簡単に使えることを理由に defineProperty
を削除して defineProperties
だけを残すことを提案した。しかしこの方法だと、別の場所で計算された名前を持つプロパティの定義が難しくなる。ES3.1 は別の場所で計算された値をオブジェクトリテラルのプロパティ名に配置する構文的な方法を持たなかった。結局 ES3.1 は個別の引数として名前を渡して単一のプロパティを定義する defineProperty
とプロパティマップを使って複数のプロパティを一度に定義する defineProperties
を両方持つことになった。ES5 が定義するオブジェクトリフレクション関数の完全なリストを図 33 に示す。
関数の名前 | 機能 |
Object.create |
与えられた新しいオブジェクトをプロトタイプとする新しいオブジェクトを作成する。プロパティマップが指定されたときは、それが指定するプロパティをオブジェクトに追加する。 |
Object.defineProperty |
プロパティ記述子に基づいて、新しいプロパティを作成あるいは既存のプロパティの定義を更新する。 |
Object.defineProperties |
プロパティマップが指定する通りにプロパティの定義を作成あるいは更新する。 |
Object.getOwnPropertyDescriptor |
与えられた名前のプロパティの記述子オブジェクトを返す。プロパティが存在しなければ undefined を返す。 |
Object.getOwnPropertyNames |
オブジェクトが持つ固有プロパティの名前を表す文字列を要素とする Array を返す。 |
Object.getPrototypeOf |
引数に渡されたオブジェクトのプロトタイプオブジェクトを返す。 |
Object.keys |
オブジェクトに対して for-in を使ったときに見えるプロパティの名前を表す文字列を要素とする Array を返す。 |
Object.preventExtensions |
オブジェクトに対するプロパティの追加を禁止する。 |
Object.seal |
プロパティの追加および固有プロパティの定義変更を禁止する。 |
Object.freeze |
オブジェクトを seal した上で固有データプロパティの値を凍結する。 |
Object.isExtensible |
固有プロパティを追加できるかどうかを判定する。 |
Object.isSealed |
オブジェクトが seal されているかどうかを判定する。 |
Object.isFrozen |
オブジェクトが freeze されているかどうかを判定する。 |
アクセッサプロパティは異なる形のプロパティ記述子を通してサポートされる。アクセッサプロパティを定義するときに使われるプロパティ記述子は value
属性の代わりに get
属性と set
属性のいずれかまたは両方を持つ。例えば、あるデータプロパティへのアクセスを仲介するアクセッサプロパティは次のように定義できる:
Object.defineProperties(obj, {
x: { // パブリックなアクセッサプロパティ
set: function(value) {this.privateX = value},
get: function() {return this.privateX}
},
privateX: { // 「プライベート」なデータプロパティ
value: 0,
writable: true
}
});
リフレクションベースのインターフェースに加えて、ES3.1 ではオブジェクトリテラルでアクセッサプロパティを定義する構文的なサポートも追加された。この機能は 4 つの主要なブラウザのうち 3 つで既に利用可能だったので、新しい構文を追加するときの基準に合格していた。アクセッサプロパティをオブジェクトリテラルで定義するには、function
を get
または set
に置き換えた関数定義をリテラルの中に記述する。この例を示す:
var obj = {
privateX: 0, // 通常のデータプロパティ
set x(value){this.privateX = value}, // アクセッサプロパティ x のセッター
get x(){return this.privateX}, // アクセッサプロパティ x のゲッター
get negX(){return -this.privateX} // アクセッサプロパティ negX はゲッターだけを持つ
};
こういった新しい機能をサポートするには ES1 の仕様で最初に定義された内部のオブジェクトモデルを拡張し、その一部をオブジェクトリフレクション API を通して公開する必要がある。これはオブジェクトモデルの用語を再検討する機会にもなった。ES1 は各プロパティが一つの値といくつかの属性を持つと説明していた。ES1 における属性は ReadOnly, DontEnum, DontDelete の三つである。これらの属性は状態を持たず、プロパティに関連付けられて何らかの特性の有無を示すマーカーだった。ES3.1 の設計者は属性をプロパティ記述子オブジェクトのプロパティとして具象化することを望んだ。これは内部モデルを変更することで達成された。具体的には、ES3.1 において ES1 の属性はオブジェクトの各プロパティに関連付く真偽値の状態変数となり、プロパティの値は属性と同列の一つの状態変数という新しい概念となった。属性を表すのに内部で使われる命名規則も変更され、内部メソッドに使われていた二つの鍵括弧を使うパターンが採用された。この拡張されたモデルではアクセッサプロパティが [[Get]] と [[Set]] という属性で表される。[[Get]] と [[Set]] の値はゲッター関数およびセッター関数 (あるいはデフォルトの関数を表す undefined
) であり、これらの関数はプロパティの値に対する参照および代入で呼び出される。このとき、データプロパティとアクセッサプロパティの区別は [[Value]] 属性を持っているかどうか (同じことだが、[[Get]] 属性と [[Set]] 属性が両方とも未設定かどうか) を確認することで行える。
アクセッサプロパティをサポートするには [[Get]], [[Put]], [[CanPut]] という ES1 で最初に定義された内部メソッドの仕様を更新する必要があった。また、オブジェクトリフレクション API が利用するプロパティ記述子をサポートするには [[DefineOwnProperty]], [[GetOwnProperty]], [[GetProperty]] という内部メソッドが必要になった。しかし、こうして定義されるリフレクション API は依然として十分でなかった。ES3.1 では for-in
文によるプロパティキーの列挙、Object.getOwnPropertyNames
関数、そして Object.keys
関数の意味論が非形式的な散文を使って規定されていたからである。
オブジェクトリフレクション API を設計する最後のステップは、プロパティ属性を公開するにあたってプロパティ記述子オブジェクトのプロパティ名で使うための利便性に優れる一貫した命名規則を見つけることだった。特に DontEnum や ReadOnly といった名前は一貫性に欠け使いにくいという懸念があった。これは真偽値のフラグとして扱われるとき特に問題になる。例えばプロパティを列挙可能にしたいときは「DontEnum を false
に設定する」という二重否定を考えなければならない。2008 年の初め、ES42 関連のスレッドで Neil Mix [2008b] は属性の名前を「enumerable」「writable」「removable (DontDelete の代わり)」とするべきだと主張した。Mark Miller [2008b] はこの命名に好意的に捉え、「属性の名前は禁止する操作ではなく許可する操作を表すべき」という設計指針を提案した。さらに彼は「デフォルトは拒否」というセキュリティベストプラクティスに従うことも提案した。このプラクティスに従うと、プログラマはプロパティを定義するときに有効化したい属性を全て明示的に指定しなければならない。
オブジェクトリフレクション API は ECMAScript の過去のバージョンに存在しない新たな機能を提供する。この API を使うとプログラムは既存のプロパティの属性を変更でき、プロパティをデータプロパティからアクセッサプロパティにすることもできる。そういった変更の機能を無効化するための属性が必要かどうかについて検討があった。この属性の名前として「dynamic」「flexible」「fixed」が選択肢に上がった。ただ真偽値のプロパティ属性を新しく追加する影響について懸念の声があった。新しい属性を表現するためのビットを実装が持っていなかったら? その後 ES3.1 ワーキンググループはプロパティの属性を変更することは (アトミックに) 現在のプロパティの属性を確認し、プロパティを削除し、改変した属性を持った同じ名前のプロパティを作ることと等価である事実に気が付いた。この事実に基づけば、削除可能性と改変可能性を表す属性は一つで済む。この結果 DontDelete (removable) 属性は「Configurable6」に改名され、その意味も変更された。ES5 におけるプロパティ属性の状態遷移図 [Harel 2007] のドラフトは Mark Miller によって作成され ECMAScript wiki に投稿された [2010b; 図 34]。この図によると、configurable 属性が false
の場合でもプロパティの writable 属性を true
から false
に変更できる。この例外はセキュリティサンドボックス語が特定の組み込みプロパティを「非 configurable かつ writable」から「非 configurable かつ非 writable」に変えられるように存在する。
Douglas Crockford は JavaScript アプリケーションの構築におけるプロトタイプを活用するスタイルのオブジェクト指向プログラミングの提唱者であり、彼は明示的に渡されるプロトタイプを使ってオブジェクトを作成する beget
という関数を広めた。ES5 の Object.create
関数は省略可能な二つ目のパラメータにプロパティマップを取る点を除けば事実上この beget
関数である。この例を示す:
// Crockford のスタイルを使ったオブジェクトの作成
var point1 = beget(protoPoint);
point1.x = 0;
point1.y = 0;
// ES5 の宣言的スタイルを使ったオブジェクトの作成
var point2 = Object.create(protoPoint, {
x: {value: 0},
y: {value: 0}
});
Allen Wirfs-Brock は JavaScript プログラマが宣言的スタイルを採用し、実装がこのパターンを認識してオブジェクトの作成を最適化することを期待した。しかし実際には、ES5 で可能になったこのパターンは利便性の問題があったために広く採用されることはなかった。その問題はデフォルトのプロパティ属性の選択にある。JavaScript 1.0 までさかのぼる仕様として、暗黙な代入で作成されたプロパティは {writable: true, enumerable: true, configurable: true}
と等価なプロパティ属性を持つ。これに対して ES5 の属性記述子は「デフォルトは拒否」のポリシーを使って設計されたので、Object.create
の宣言的スタイルを使うと全ての属性のデフォルトの値が false
になる。この例を示す:
// Crockford のスタイルで Object.create を使う
var point1 = Object.create(protoPoint);
point1.x = 0;
point1.y = 0;
// point1.x の属性: writable:true, enumerable:true, configurable:true
// point1.x の属性: writable:true, enumerable:true, configurable:true
// 宣言的スタイルで Object.create を使う
var point2 = Object.create(protoPoint, {
x: {value: 0},
y: {value: 0}
});
// point2.x の属性: writable:false, enumerable:false, configurable:false
// point2.y の属性: writable:false, enumerable:false, configurable:false
ES5 を使って beget
の例と同じ効果を得るには、次のように書かなければならない:
// ES5 の機能を使いつつ、ES5 より前のバージョンと同じ属性値を持つインスタンスを作成する
var point2 = Object.create(protoPoint, {
x: {value: 0, writable:true, enumerable:true, configurable:true},
y: {value: 0, writable:true, enumerable:true, configurable:true}
});
JavaScript で古くから使われてきた緩いデフォルトを使い続けることを望む多くのプログラマにとって、この形式は冗長すぎた。実際のコードでは引数を一つだけ受け取る Object.create
で新しいオブジェクトを作成する処理や、作成済みのオブジェクトのプロパティを Object.defineProperties
で操作あるいは定義する処理はよく行われたものの、新しいオブジェクトのプロパティを定義するために引数を二つ受け取る Object.create
が使われることはほとんどなかった。
20.1.3 オブジェクトの完全性とセキュリティ機能
Netscape 3 で導入された HTML の <script>
要素の src
属性を使うと、ウェブページは JavaScript コードを複数のウェブサーバーから読み込めるようになる。最もよく使われた形式の読み込みでは全てのスクリプトが単一の JavaScript 実行環境に読み込まれ、グローバルな名前空間を共有する。このためサイトをまたいだスクリプトであっても直接対話でき、この特性はマッシュアップ語アプリケーションを可能にした。異なるサイトにあるスクリプトを読み込む機能は広く利用され、広告ベースのウェブビジネスモデルを可能にする上で重要な役割を果たした。しかし異なるサイトのスクリプトは改竄される可能性があり、読み込み元のウェブページが持つスクリプトや他のサイトから読み込んだスクリプトと干渉する可能性もある。最終的にウェブ開発者はサードパーティのスクリプトがパスワードなどの機密ユーザーデータを盗んだり、ユーザーを騙すためにページの振る舞いを改変したりできることを発見した。2007 年にはウェブ広告ブローカーによって意図せず配布された悪意ある広告が観測され始めていた。ブラウザベンダーはコンテンツセキュリティポリシー (CSP) をはじめとした HTML および HTTP のレベルの様々な機能でこの問題を解決しようとしたものの、そのレベルの機能では直接解決できない低レベルな JavaScript の脆弱性も多くあった [Barth et al. 2009]。
ES3.1 ワーキンググループに参加している間、Douglas Crockford [ADsafe 2007] と Mark Miller [Caja Project 2012; Miller et al. 2008] は信頼できないサードパーティ JavaScript コードのセキュアな実行をホストするための JavaScript 実行サンドボックス用の技術を活発に開発していた。ES3.1 の厳しい後方互換性要件は既知のサードパーティスクリプト関連の脆弱性の多くを消し去れないことを意味したものの、Crockford と Miller の二人は互換性を保ったまま取り除ける脆弱性を削除すること、そしてセキュアなサンドボックスの作成で役立つ機能を追加することが必要だと主張した。特に Mark Miller はオブジェクト機能モデルに基づくサンドボックスの作成で必要になる機能に関心を持っていた [Miller 2006]。
最大の問題は JavaScript オブジェクトの改変可能性だった。デフォルトでは標準ライブラリのオブジェクトを含む全てのオブジェクトがそれへの参照を持つ任意のコードから完全に改変可能となる。プロパティはメソッドを含めて追加でき、値を変更でき、削除もできる。これは直接参照されるオブジェクトおよび何らかのルートオブジェクトから間接的に参照できるオブジェクト全てについて言える。ES3 はオブジェクトが持つプロトタイプオブジェクトへの参照を直接変更する手段を提供しなかったものの、Internet Explorer を除く全ての主要ブラウザは非標準のプロパティ __proto__
を実装しており、これを使うとオブジェクトのプロトタイプ継承チェーンを改変できた。ES3 において、この至る所に存在する改変可能性の唯一の例外は ReadOnly もしくは DontDelete の属性が指定された少数の組み込みプロパティだけだった。
Mark Miller と Douglas Crockford の二人は信頼できないコードにオブジェクトを渡す前にそのオブジェクトのプロパティを固定する機能の追加を望んだ。この機能を使うとサンドボックスに公開される組み込みライブラリのオブジェクトをセキュアにでき、サンドボックスをホストするコードは信頼できないコードに渡すオブジェクトの安全性を保証できるようになる。DontDelete 属性が Configurable 属性に変わったので、Object.defineProperty
を使ってプロパティを非 Configurable にすれば個別のプロパティをセキュアにする基本的な機能は提供される。しかしそれだけでは信頼できないコードが受け取ったオブジェクトに新しいプロパティを追加することを阻止するには十分でない。新しいプロパティを追加できると、信頼できないコードは受け取ったオブジェクトが継承する振る舞いをオーバーライドし、ユーザーのプライベートなデータを漏洩させるための秘密の通信チャンネルを開くかもしれない。ES5 において、この問題は内部で [[Extensible]] と呼ばれる状態を新しく各オブジェクトに関連付けることで解決された。オブジェクトが新しく作られたとき [[Extensible]] は true
であり、[[Extensible]] を false
に設定すると固有プロパティが追加できなくなる。また実装は [[Extensible]] が false
であるオブジェクトの [[Prototype]] の変更を可能にする一切の言語拡張の提供を禁止される。最後に、[[Extensible]] を一度 false
に設定すると true
に戻すことはできない。
Object.isExtensible
関数がオブジェクトの [[Extensible]] の状態を検査する API を提供する。Object.preventExtensions
関数は [[Extensible]] を false
に設定する。Object.freeze
は利便性のために提供される関数で、[[Extensible]] を false
に設定し、全ての固有プロパティの [[Configurable]] と [[Writable]] を false
に設定する。Object.freeze
を使うとオブジェクトの直接的な状態を完全に不変にできる。Object.seal
関数は基本的に Object.freeze
と同様だが、[[Writable]] を false
にしない点だけが異なる。Object.seal
を使うとオブジェクトに設定されたプロトタイプとプロパティを固定しつつも、データプロパティの値は改変可能なようにできる。
もう一つの大きな懸念はグローバルオブジェクトにどこからでもアクセスできることだった。ECMAScript ではグローバルオブジェクトのプロパティにグローバルスコープの変数が格納される。標準ライブラリに含まれる名前の付いたオブジェクトは全てグローバルオブジェクトのプロパティとして存在し、JavaScript のホスト環境は環境特有のオブジェクトと API 関数をグローバルオブジェクトに追加する。例えばブラウザではグローバルオブジェクトが window
オブジェクトと等価であり、このオブジェクトが現在のページの DOM オブジェクトやブラウザ API への完全なアクセスを提供する。通常サンドボックスはグローバルオブジェクトのプロパティの全てまたは一部に対するアクセスを制限するか、そうでなければグローバルオブジェクトのプロパティの一部の代替バージョンを提供する。理論上、この処理はサンドボックス内のコード全体を新しい字句的スコープで囲んだ上でグローバルオブジェクトのプロパティの代わりになる束縛を提供するかプロパティを undefined
にして束縛を隠蔽すれば完了する。しかし字句的スコープに隠蔽されていないグローバルオブジェクトへのアクセスを取得する方法が JavaScript 1.0 から存在する:
function getGlobalObject() {
// 関数を直接呼び出すと、this はグローバルオブジェクトになる
return this;
}
getGlobalObject().document.write("pwned");
ES5 より前の ECMAScript における関数を直接 (オブジェクトが先頭に付くメソッド呼び出しでない形で) 呼び出したときの振る舞いは、暗黙の引数 this
に null
を設定し、全ての関数は処理の開始時に this
が null
なら this
をグローバルオブジェクトに置き換えるというものだった。後方互換性のため既存のコードに対するこの振る舞いを変えることはできない。しかし ES5 は strict モードを通して異なる振る舞いにオプトインする選択肢を新しく書かれるコードに提供する。ES5 で strict モードの関数は this
引数をグローバルオブジェクトで置き換えない。そのためサンドボックスは strict モードのコードだけをサンドボックス内で実行することでグローバルオブジェクトにどこからでもアクセスできる問題を回避できる。
ES5 の策定が進行していたとき、図 35 に示すような悪意あるエクスプロイトがウェブで実際に観測され始めていた。ES3 ではオブジェクトリテラルを使って作成されたオブジェクトが Object.prototype
を継承し、オブジェクトリテラルは [[Put]] という内部メソッドを使ってリテラル内に指定されたプロパティを新しいオブジェクトにインストールすると規定される。しかし [[Put]] を使ってオブジェクトのプロパティに値をインストールするとき、オブジェクトは最初にプロトタイプ継承チェーンをたどって同名のプロパティが存在するかどうかを判定する。そして同名のセッタープロパティが見つかると、そのセッター関数が実行される。そのため何らかのセッター関数が Object.prototype
にインストールされていると、同名のプロパティを持つオブジェクトリテラルが使われるたびにそのセッター関数がリテラルに渡された値と共に呼ばれる。
// ウェブページがオブジェクトリテラルの "secret" というプロパティに
// 重要な情報を格納していることが判明したと仮定する
function setupToStealSecret() {
// 非標準なゲッター/セッター API (ES5 より前に使われていた) を使って
// ゲッターとセッターの組をプロトタイプに定義する
Object.prototype.__defineSetter__("secret", function(val) {
this.__harmlessSoundingName__ = val; // 値を他の場所に格納する
exploitTheSecret(val, this);
});
Object.prototype.__defineGetter__("secret", function() {
// 他の場所で処理が止まらないように、別の場所に格納した値を返す
return this.__harmlessSoundingName__;
});
}
setupToStealSecret();
// 以降のコードで "secret" という名前のプロパティを持ったオブジェクトリテラルを使って
// オブジェクトを定義すると、それだけで情報が外部に漏れる
var objectWithSecret = {
secret: "password", // 継承されたセッターが起動する
// 他のプロパティ...
};
この抜け穴の修正はオブジェクトリテラルの意味論の破壊的な変更だったものの、これはセキュリティ脆弱性を潰すためにブラウザベンダーが望んで受け入れる種類の変更だった。実際の仕様の変更は簡単に行えた: ES5 では新しいオブジェクトのプロパティを作成するのに [[Put]] の意味論は使われず、新しい内部メソッド [[DefineOwnProperty]] が使われるようになった。[[DefineOwnProperty]] は必ず継承されたプロパティを無視して新しいプロパティをオブジェクトに直接作成する。
ES5 は JavaScript をよりセキュアにすることに関して小さな一歩しか踏み出せなかった。ES5 の作業が進んでいたとき、Douglas Crockford は Secure ECMAScript (SES) ワーキンググループを TC39 に設立することを提案した。その目的 [Crockford 2008d; TC39 2008b] は後方互換性の制約に縛られないセキュアな ECMAScript 方言を作成する可能性を探ることだった。SES ワーキンググループは 2008 年から 2009 年にかけて 4 回の会合を開き、信頼できないコードのセキュアな評価を行う既存の JavaScript ソリューションをいくつかレビューした [TC39 2008e]。最終的に新しい個別の方言を TC39 で標準化するというアイデアは放棄されたものの、オブジェクト機能モデルなどの SES で検討されたコンセプトは Harmony の活動に大きな影響を与えた。Ankur Taly ら [2011] は strict モードと他の ES5 機能がマッシュアップフレンドリーでセキュアな ECMAScript の部分集合の作成を可能にすることを形式的に説明した。
20.1.4 アクティベーションオブジェクトの削除
ES5 より前の ECMAScript 仕様では ECMAScript オブジェクトを明示的に使って ECMAScript 言語におけるスコープの意味論を定義していた。スコープの輪郭語はそれぞれアクティベーションオブジェクトによって表される ── これは通常の ECMAScript オブジェクトであり、そのプロパティが輪郭に対応するコードによって作成される変数と関数の束縛を表す。入れ子になったスコープはアクティベーションオブジェクトのリストとして表され、束縛が参照されるたびにこのリストが検索される。アクティベーションオブジェクトのアクセスとユーザープログラムが定義するオブジェクトプロパティのアクセスの両方において、束縛を参照する言語機能は同じプロパティアクセス意味論演算子を使用する。ES1 とそれ以降の仕様にはアクティベーションオブジェクトが仕様を記述する手段としてだけ存在し、ECMAScript プログラムからは観測できないとある。しかしプロパティアクセスの意味論はエッジケースで予期せぬ振る舞いを生んでおり、それは完全に使用準拠なエンジンでも観測できた。様そういったエッジケースの意味論をどの程度忠実に実装するか (それとも全て無視するか) は実装ごとに異なっていた。
奇妙な点の一つが、アクティベーションオブジェクトが新しく作成されるオブジェクトに対するデフォルトのプロトタイプ Object.prototype
を継承することである。これは Object.prototype
のプロパティが全てのアクティベーションオブジェクトに継承され、それぞれのアクティベーションオブジェクトからローカル束縛のように見えることを意味する。この束縛は外側のスコープにある同名の束縛を隠蔽する。
束縛の解決は動的に起こると仕様が定めている。そのためアクティベーションオブジェクトに対するプロパティ検索処理を応用すれば、関数の呼び出しに先立って Object.prototype
に束縛を追加しておくことで、呼び出された関数における任意の自由参照に割り入ることができる。この例を示す:
var originalArray = Array;
function AltArray() {
// 組み込みの Array コンストラクタはこの関数で置き換わる
//...
}
// Array ではなく AltArray を呼び出すようにした状態で関数を呼び出す
Object.prototype.Array = AltArray;
somethingThatFreelyReferencesArray();
// AltArray の束縛を削除する
delete Object.prototype.Array;
ES3 仕様が持つもう一つの奇妙な点は try
文の catch
節におけるパラメータの扱いである。catch
節のパラメータは catch
節の本体を囲む新しいスコープにローカルな字句的スコープの束縛となるのだが、スコープの輪郭を ECMAScript オブジェクトを使って表す事実がこの意味論に問題を起こす。ES5 仕様 [Lakshman and Wirfs-Brock 2009, Annex D] はこの問題を次のように説明している:
12.4: 第 3 版では、
try
文のcatch
節に渡される例外パラメータの名前を解決するためのスコープとしてnew Object()
を呼び出した結果かのようなオブジェクトが作成される。実際の例外オブジェクトが関数で、それがcatch
節内で呼ばれた場合、このスコープオブジェクトが呼び出しのthis
として渡される7。その後、呼ばれた関数の本体はthis
に新しいプロパティを定義でき、定義された値は関数が返った後でもcatch
節のスコープ内から見える識別子となる。第 5 版では、例外パラメータが関数のときはthis
にundefined
が渡される。
2008 年のほぼ全体を通して、ワーキンググループは const
宣言を新しい版に含めるつもりでいた。4 つの主要なブラウザの 3 つで、意味論は違ったものの、const
が利用可能だったからである。const
はブロックレベルの字句的スコープとする計画であり、これまでの版で使われてきたレガシーのスコープモデルがさらに複雑になると予想された。
この問題を解決するため、Allen Wirfs-Brock はスコープと束縛の仕様レベルのモデルを新しく作成した。このモデルでは識別子の解決に ECMAScript オブジェクトの意味論は使われず、環境レコード (environment record) と環境 (environment) が新しく導入される。環境レコードは単一のスコープ輪郭に含まれる束縛を保持するコンテナであり、環境は ECMAScript プログラムの任意の時点における識別子解決のコンテキストを与える環境レコードの順序付きリストである。環境レコードにはいくつか種類があり、それらを使ってグローバルスコープ、関数スコープ、ブロックスコープ、文スコープが区別される。一方で環境は個別の束縛の定義、探索、値の変更を行うための仕様レベルの共通プロトコルを公開し、変数やその他の束縛の宣言あるいはアクセスを行う言語機能を規定するときにこのプロトコルが使われる。
しかし結局 const
宣言は将来の「Harmony」まで延期された。急いで const
を導入すると、将来より包括的な形でブロックスコープ宣言を設計するときに邪魔になるかもしれないとワーキンググループは判断した。ただ新しいスコープモデルはレガシーなスコープが持つ既知の奇妙な点を ES5 で修正するために利用され、ES6 における包括的な宣言文の導入に向けた基礎となった。
20.1.5 その他の ES5 の機能
図 33 に示したオブジェクトリフレクション関数に加えて、ES5 には次の新しい関数、メソッド、プロパティが標準で組み込まれる:
- オブジェクトとデータ交換フォーマット JSON を表す文字列の間で変換を行う
JSON.parse
とJSON.stringify
Array.prototype
の新しい 9 つのメソッド:indexOf
,lastIndexOf
,every
,some
,forEach
,map
,filter
,reduce
,reduceRight
String.prototype
の新しいメソッド:trim
Date
:Date.now
; 日時フォーマット ISO 8601 のパースと生成を行うその他の拡張Function.prototype
の新しいメソッドbind
と関数インスタンスの新しいプロパティname
これ以外の変更や改善には次のようなものがある:
with
文とcatch
節が受け取るパラメータのスコープの意味論に対する修正[]
構文を使った文字列に対する配列風アクセス- 正規表現の文法に対する細かな修正
- 正規表現リテラルを評価するたびに新しい
RegExp
オブジェクトを作成しなければならないことが明確化 - 正当でない正規表現リテラルのエラー報告が早く行われるように
- グローバルオブジェクトのプロパティ
undefined
,NaN
,Infinity
が読み込み専用の値を持つように - 仕様のアルゴリズムが組み込みの
Object
,Array
などの現在の値ではなく最初の値を使うように - 仕様の意味論に対する様々な明確化や修正を非規定的な形でまとめた Annex D と Annex E が追加
20.2 実装とテスト
2008 年 7 月にオスロで開かれた Ecma TC39 会合で、TC39 は ES3.1 仕様の公開に進む前に相互運用可能な二つの ES3.1 実装を用意することに合意した8。この「相互運用可能な二つの実装」という要件の主なモチベーションは、TC39 が技術的に実現不可能あるいは既存のウェブコンテンツと非互換な仕様を標準化していないことの確認だった。実装の一つは Mozilla が提供を約束した。マーケットにおける Microsoft の立ち位置、そして歴史的に IE の更新が遅かった事実を理由に、Microsoft は ES3.1 の検証プロセスの一環としてブラウザにホストされるプロトタイプ実装を誰でも利用できる形で公開し、ES3.1 への貢献を示すべきだという考えが TC39 内に根強く存在した。当時 TC39 の計画では ES3.1 を 2009 年 6 月の Ecma 総会会合で公開できるように準備を進めていた。これを可能にするには 2009 年 3 月の TC39 会合でどの機能を含めるかを決定する必要があり、そのためには 2 月か 3 月の間に相互運用性のテストを行う必要があった。
このころ ECMA-262 公式の準拠検証テストスイートは存在せず、ES3.1 の新機能に対するテストも全くなかった。全ての実装は独自にアドホックなテストスイートを持ち、加えて Microsoft 以外の全てのブラウザは Mozilla の JavaScript テストスイートも利用していた。Microsoft は Mozilla の JavaScript テストスイートで使われた Mozilla Public License に関して懸念を持っていた。Microsoft が望んだのは Ecma を通して MIT あるいは BSD スタイルのライセンスで公開されるテストスイートだった。
2008 年 10 月、Pratap Lakshman は Internet Explorer にホストされる ES3.1 実装とそれに附随するテストスイートの作成という両面作戦を開始した。
実装されるテストケースはコミュニティに公開される予定だった。テストケースの目標はコードカバレッジの最大化とされた。ここで「コード」とは仕様内の疑似コードを意味する。各テストケースは最新の仕様ドラフトにおける節番号とアルゴリズム内のステップ番号からなる名前が付き、一つのテストが一つの .js
ファイルに配置された。テストファイルの命名規則の説明を図 36 に示す。
sectionNumber-algorithmStepNumber-testNumber-s.js
ここで
sectionNumber: 仕様における節番号
algorithmStepNumber: このテストが検証する要件を持つステップ番号
testNumber: (省略可能) アルゴリズムの特定のステップにテストケースが複数あるなら付く
-s: (省略可能) テストが strict モード用なら付く
Lakshman は 900 個以上のテストケースと個別のテストケースを実行して報告する簡単なテストハーネスを実装した。テストケースの例を図 37 に示す。
// Test Subclause 10.4.2 Alorithm 3 Step 1 Strict mode
var testName =
"Eval code in strict mode-cannot instantiate variable in calling context";
function testcase() {
eval("'use strict';var __10_4_2_3_1_s = 1");
try{
__10_4_2_3_1_s;
} catch(e) {
if (e instanceof ReferenceError)
return true;
}
}
2009 年 1 月の TC39 会合 [Horwat 2009] では Pratap Lakshman が JSCRIPT.dll
の実験バージョンをちょうど公開されたばかりの Microsoft Internet Explorer 8 Release Candidate 1 に統合したもの使って ES3.1 のプロトタイプ実装のデモを行った。このデモでは準拠検証テストスイートの他に新しい言語機能も披露された。この取り組みは広く好意的に捉えられ、Waldemar Horwat による会合の報告書には「原住民は非常に喜んだ9」と記された。
Microsoft はテストを Ecma に提供し、加えて「ES5conform」 [2009] という名前でオープンソースプロジェクトポータル codeplex.com でも公開した。ほぼ同じころ、Google は Chrome の JavaScript エンジン V8 を開発する中で作成された ES3 用テストスイートをオープンソースで公開すると発表した [Hansen 2009]。このテストスイートは「Sputnik」と呼ばれ、5000 個以上のテストから構成された。
2010 年、ES5conform と Sputnik は「Test262」と呼ばれる Ecma TC39 が管理するテストスイートの中核となった。ソフトウェアパッケージの管理と配布を行うというのは Ecma TC にとって非常に大きな変革であり、このためにポリシーやライセンスに関する問題をいくつも解決する必要があった。David Fugate は Test262 開発の初期にあたる ES5 フェーズを指揮した。ES6 用の Test262 は Fugate の後任 Brian Terlson が担当し、ES6 より後は Leo Balter が担当した。現在 Test262 は TC39 における規格策定プロセスの重要な要素であり、全ての新しい ECMAScript 機能は規格へ追加される前にテストが書かれなければならないことになっている。2018 年 8 月の時点で Test262 は 61,877 個のテストから構成される。Test262 の成功は実行可能な仕様は必要でないことを TC39 に納得させる助けとなった。
-
このメールフォーラムの名前は 2009 年 3 月に es5-discuss へと変更された。 ↩︎
-
この「他のコード」は未知の場所から取得された信頼のおけないコードである可能性がある。 ↩︎
-
「...関数」ではなく「...メソッド」と呼ばれることもある。名前空間として使われるオブジェクトと振る舞いの抽象化として使われるオブジェクトの違いは概念的なものであり、実際の言語の意味論を反映したものではない。一部の JavaScript プログラマは「メソッド」という言葉で両者を区別したが、区別しない JavaScript プログラマもいた。 ↩︎
-
個々の機能は共通の概念と構文要素を使って設計されるべきだ、ということ。 ↩︎
-
この提案は直接には公開されていないものの、Miller のコメント [2008a] に提案のほぼ全文が含まれている。 ↩︎
-
訳注: 例外パラメータの関数に対する参照がスコープオブジェクトのプロパティに対する参照とみなされ、メソッド呼び出しを行ったかのような処理が起こる。 ↩︎
-
この節には Pratap Lakshman が貢献した。 ↩︎
-
訳注: 原文は "There was much rejoicing among the natives." モンティパイソンの映画 Monty Python and the Holy Grail に "and there was much rejoicing" という台詞があり、大きな成功を喜ぶ様子を表すフレーズとしてハッカーコミュニティで使われていた。 ↩︎