5. Mocha から SpiderMonkey まで
1995 年では通年、1996 年ではほぼ通年、Brendan Eich は Netscape で JavaScript エンジン語1にフルタイムで取り組む唯一の開発者だった。1996 年 8 月にリリースされた Netscape 3.0 に付属する JavaScript 1.1 は、依然としてほとんどが 1995 年 5 月に 10 日間で作られたプロトタイプのコードから構成されていた。Netscape 3.0 リリースの後、Eich はそろそろエンジン語の技術的負債2を返済し、JavaScript を「よりクリーンな」言語にする作業を始める時だと感じていた。一方で Netscape の経営層は彼が言語仕様に取り組むことを望んだ。経営層は仕様が無いことに関する Microsoft からの批判を気にしており、目前に迫った標準化活動には入力として仕様が必要になるだろうと見ていた。しかし Eich は反対し、Mocha の再実装から始めることを望んだ。仕様を書くには Mocha の実装を注意深くレビューする必要がある。Mocha を書き直しながらレビューを行うのが最も効率的だろうと彼は考えた。そうすれば仕様を正式に決める前にオリジナルの Mocha に含まれる設計ミスを訂正することもできる。
この議論に腹を立てた Brendan Eich はオフィスを離れ、二週間にわたって自宅で働いた。その間に Eich は JavaScript エンジンの中心部の再設計と再実装を行い、結果として速度、信頼性、柔軟性の増した実行エンジンが完成する。彼は JavaScript の値を判別可能 union語 として表すのを止め、代わりにプリミティブ値の即値を含むタグ付きポインタを使うようにした。また入れ子になった関数、関数式、switch
文といったオリジナルのエンジンに間に合わなかった機能も追加され、さらに参照カウントによるメモリ管理はマーク・アンド・スイープによるガベージコレクタに置き換えられた。
Eich がオフィスに戻ると、その新しいエンジンが Mocha を置き換えた。初期版の Netscape 開発者の一人 Chris Houck が JavaScript チームの二人目のフルタイムメンバーとして Eich に加わった。Houck は新しいエンジンを Beavis and Butt-Head Do America という映画の下品な台詞から「SpiderMonkey3」と名付けた [Judge et al. 1996]。Clayton Lewis がチームにマネージャとして参加し、Norris Boyd を雇い入れた。仕様の作成で Eich を補助するためにテクニカルライターの Rand McKinny が割り当てられた。
Brendan Eich は Netscape 4.0 の一部としてリリースされる JavaScript 1.2 になるものとして言語の改良を続けた。Netscape 4.0 の最初のベータリリースは 1996 年 12 月に行われ、1997 年 4 月のベータでは正規表現が追加された。様々なプラットフォームにおける Netscape 4 の製品リリースは 1997 年 6 月に始まり、その年の後半で散発的に行われた。
SpiderMonkey が実装する JavaScript 1.2 という言語と組み込みライブラリは JavaScript 1.0 および JavaScript 1.1 と比べて大きく改良された。JavaScript 1.2 の主な新機能を図 10 に示す [Netscape 1997c]。ライブラリに追加された機能の多くは他の有名な言語で利用可能な機能から着想を得ている。Array
の concat
と slice
メソッドは Python のシーケンス操作をならったものであり、Array
の pop
, shift
, unshift
, splice
は同様の名前が付いた Perl の配列関数をそのまま真似ている。この他に Python からは String
の concat
, slice
, search
が、Perl からは String
の match
, replace
, substr
が取られた。正規表現を使った文字列マッチングの構文と意味論は Perl から借用された。
do
文- 文へのラベルの付与、およびラベルへの
break
/continue
switch
文- 入れ子になった
function
宣言 (レキシカルスコープ) - 関数式 (ラムダ式)
==
演算子が行っていた自動的な型強制の削除- プロパティに対する
delete
演算子が本当にプロパティを削除するように - オブジェクトリテラル
- 配列リテラル
- 正規表現リテラル
- 正規表現マッチングを行うメソッドを持った
RegExp
オブジェクト - 全てのオブジェクトに対する
__proto__
疑似プロパティ - 新しい配列メソッド:
push
,pop
,shift
,unshift
,splice
,concat
,slice
- 新しい文字列メソッド:
charCodeAt
RegExp
を使ったfromCharCode
(ISO latin-1),match
,replace
,search
,substr
,split
- 関数オブジェクトの
parity
プロパティ - 関数オブジェクトとその引数オブジェクトが異なるオブジェクトに
- 関数の仮パラメータとローカル宣言に引数オブジェクトの名前付きプロパティからアクセスできるように
arguments.callee
watch
/unwatch
関数import
/export
および署名付きスクリプト
文レベルの新機能は以前の JavaScript に存在しない文であって C の仲間の言語に慣れているプログラマが期待するものを提供する。do
文は JavaScript 1.0 に欠けていた C の do
文に似たものであり、同じ構文とほぼ同じ意味論が採用されている。ラベル付き文とラベル付き文への break
/continue
は Java が持つ同じ機能を真似て作られた。こういった機能があると、入れ子になった反復や switch
文からの多段階の早抜け、あるいは反復でないコードブロックからの早抜けが可能になる。C や Java と同様、JavaScript 1.2 の switch
文はケース選択式のコンパイル時評価を持つ [Eich et al. 1998, jsemit.c
757–776 行]。
JavaScript 1.0 と JavaScript 1.1 で関数の定義はスクリプトのトップレベルでグローバル宣言を使って行うしかなかった。JavaScript 1.2 では関数を他の関数の内部でローカル宣言を使って定義できる。そういった内部関数 (inner function) の定義は任意に深く入れ子にできる。内部関数はレキシカルスコープを持ち、内部関数に含まれるローカル宣言は外側のスコープが持つ同じ名前の変数宣言を隠蔽する。JavaScript 1.0 と JavaScript 1.1 では言語が var
宣言と function
宣言をスクリプトの先頭まで論理的に「巻き上げ」ていた ── つまりトップレベルの宣言はスクリプトの先頭に、関数ローカルの宣言は関数本体の最初にあるものとして評価が行われた ── ので、変数や関数の前方参照が可能だった。JavaScript 1.2 では入れ子になった関数宣言も外側の関数の最初にまで巻き上げられる。同名の関数が複数回宣言されるときは、外側の関数のソースコード上で最後に現れる関数宣言の本体がその名前に束縛される。
加えて JavaScript 1.2 は関数定義を基本的な式として書けるようにすることでラムダ式 (lambda expression) を提供する。ラムダ式は関数式 (function expression) とも呼ばれ、名前が省略可能なことを除いて function
宣言と同じ構文を持つ。名前が与えられた場合、関数式は function
宣言に束縛が付いたものとみなされ、巻き上げが起こる。名前を持たない関数式は無名関数 (anonymous function) を定義する。いずれの場合でも、実行時に関数式が評価されるたびに新しいクロージャが作成される。引数オブジェクトに callee
プロパティが追加されたので、そういった名前を持たないクロージャからも再帰的に自身を参照できる。
配列リテラルとオブジェクトリテラル4は Python 言語が持つ同様の機能から着想を得ている。配列リテラルは Array
オブジェクトの要素を作成、初期化するための簡潔な構文を提供する。例えば JavaScript プログラマは次のコードを書けるようになる:
var p2 = [1,2,4,8,16,32,64];
次のように書く必要はない:
var p2 = new Array();
p2[0] = 1;
p2[1] = 2;
p2[2] = 4;
// ...
同様に、オブジェクトリテラルはプロパティを関連付けた状態でオブジェクトを作成するための簡潔な構文を提供する。オブジェクトリテラルを使うと、プログラマは次のコードが書けるようになる:
var origin = {x: 0, y: 0};
次のように書く必要はない:
var origin = new Object;
origin.x = 0;
origin.y = 0;
オブジェクトリテラルと関数式を組み合わせると、メソッドを持ったクラスレスなオブジェクトを簡単に定義できる:
function Point(x, y) {
return {
x: x,
y: y,
distance: function (another) {
return Math.sqrt(Math.pow(this.x - another.x, 2) + Math.pow(this.y - another.y, 2));
}
}
}
var origin = new Point(0, 0);
alert(origin.distance(new Point(5, 5)));
オブジェクトリテラルと関数式の組み合わせはプロトタイプオブジェクトを定義する便利な方法も提供する。この他に新しく追加された機能として __proto__
という疑似プロパティがある。これは各オブジェクトが継承されたプロパティにアクセスするときに利用される内部的な参照に対する JavaScript プログラムからの動的なアクセスと改変を可能にするために追加された5。__proto__
を使うと、プログラムは任意に深いプロパティ継承の階層を動的に作成したり、オブジェクトが継承されたプロパティをどこから取得するかを動的に変更したりできるようになる。
JavaScript 1.2 で行った変更の中には最終的に間違いだったことが判明したものもある。import
文と export
文は Netscape 4 が提供する Java 互換のスクリプト署名メカニズム [Netscape 1997a] と共に利用されることを意図したものだった。署名付きスクリプトで定義されたグローバルな変数と関数は export
文を使って明示的にエクスポートされる関数を除いてそのスクリプトにプライベートになるという機能だったが、この機能が Netscape 以外のブラウザで採用されることはなかった。
JavaScript 1.0 と JavaScript 1.1 における ==
演算子の型強制規則はユーザーからの要望によって追加されたものの、その振る舞いが驚きに満ちていて分かりにくいと感じるユーザーもいた。そこで Brendan Eich は JavaScript 1.2 で自動的な型強制のほとんどを削除することで ==
を改修することを決断した [Netscape 1997d; Rein 1997]。二つのオペランドが同じプリミティブ型 (数値型、文字列型、真偽値型、オブジェクト型) でないとき ==
は false
を返すようになった。
JavaScript 1.2 の開発中、たとえ JavaScript 1.0 および JavaScript 1.1 から意味論を変えたとしても、<script>
タグにバージョンを表す属性を用意しておけば十分対処できるだろうという期待があった。しかし JavaScript 1.2 が製品リリースを迎えるころには、そういった形のバージョン付けがウェブ開発者たちに管理できないほど難しくなりつつあった [Rein 1997]。独自の JavaScript 実装を持つ Netscape 以外のブラウザでも動作する必要があるウェブページでは特にバージョン管理が困難だった。
-
JavaScript コミュニティで「エンジン」という言葉は JavaScript 言語の実装を指す。通常 JavaScript エンジンはパーサー、仮想マシン (あるいは同じようなランタイムサポート)、ガベージコレクタ、標準ライブラリ実装などのコンポーネントから構成される。 ↩︎
-
これは Brendan Eich による回顧的な表現である。1995 年の彼は先延ばしにしたメンテナンスを行う必要性を指して「技術的負債」という言葉は使わないだろう。 ↩︎
-
SpiderMonkey は以降の Netscape および Mozilla のブラウザにおける JavaScript サブシステムの名前となった。2020 年時点で、実際の実装技術は何度も変わっているにもかかわらず、Mozilla は SpiderMonkey という名前を使い続けている。 ↩︎
-
JavaScript 1.2 のドキュメントと ES3 の仕様において、配列リテラルとオブジェクトリテラルはそれぞれ「配列初期化子 (array initializer)」および「オブジェクト初期化子 (object initializer)」と呼ばれていた。しかし JavaScript プログラマ間の会話と JavaScript 関連の記事や書籍では「リテラル (literal)」という単語の方がよく使われる。 ↩︎
-
疑似プロパティ
__proto__
は Self の parent slot に似ていると言える。 ↩︎