3. JavaScript 1.0 と JavaScript 1.1
1995 年 12 月 4 日、Netscape Communications Corporation と Sun Microsystems は共同のプレスリリースで JavaScript を発表した [Netscape and Sun 1995; 補遺 F]。このプレスリリースでは JavaScript が「オブジェクトスクリプト言語」であり、動的に「Java オブジェクトのプロパティや振る舞いを改変する」スクリプトを書くのに利用できると説明されている。また JavaScript は「オンラインアプリケーションの容易な開発のために Java を補完する」ともある。Java と JavaScript の技術的な設計は表面的に似ているだけにもかかわらず、Netscape と Sun は二つの言語の間に強い結び付きを構築しようとしていた。Java と JavaScript の名前が似ていて密接に関係しているような印象を与えることは、現在でも混乱の元となっている。
JavaScript が「LiveScript」という名前で初めて一般に公開されたのは 1995 年 9 月のことで、Netscape Navigator 2.0 の最初のベータリリースの一部として公開された [Netscape 1995b]。Navigator 2.0 はこの後に四度のベータリリースを経て 1996 年 3 月に製品リリースを迎え、そこで JavaScript 1.0 がサポートされた。Netscape Enterprise Server 2.0 も同じ年の 3 月に出荷され [Netscape 1996f]、この製品に含まれる LiveWire というサーバーサイドのスクリプトコンポーネントにも JavaScript 1.0 が組み込まれた。
JavaScript は Netscape Navigator の中で比較的小さな目立たない機能に過ぎなかったので、その開発は Navigator 2.0 の全体的なスケジュールの制約を受け、1995 年 8 月には機能を凍結しなければならなかった。そのため JavaScript 1.0 の機能セットは実質その年の 8 月時点における Mocha 実装で動作しているか動作しているに近かった機能をトリアージしたものだった。構想された言語設計と比べて実際の機能セットは不完全で、Navigator 2.0 のリリースプロセスの間 Eich が初期の Mocha 実装のバグを修正し続けたとはいえ問題のあるバグやエッジケースの振る舞いが大量に存在した。JavaScript 1.0 リリース前に行われたインタビュー [Shah 1996] で、Brendan Eich は Java に付属する言語という JavaScript の公式の位置付け、そして初期リリースが急がれたものである事実に繰り返し言及している:
BE [Brendan Eich]: Bill Joy と私が取り組んでいる仕様に基づいて、JavaScript が他のベンダーによって実装されることを願います。小ささを保ちつつも、Java アプレットといった他のコンポーネントと共に HTML 要素の組み合わせや操作を行うための愛される手段としてウェブでユビキタスになるのを期待しています。
BE: ... 私の知る限り、ページを少しだけ賢く、少しだけ生き生きとさせるのに最もよく使われています。例えばリンクをクリックしたときに日時に応じて異なる URL語 を読み込ませるようにしたり。
...
BE: トンネルの終わりには光があるものです。確かに JavaScript はあまりにもワンマンショーすぎるので、Netscape 2.0 には忌々しい小さなバグがいくつも含まれるでしょう。ただ大きなバグには全てワークアラウンドがあるだろうと私は当て込んでいます。バグとワークアラウンドを見つけるのにチームの開発者と共に長い時間を費やしてきました。
私は 2.1 のリリースに向けてバグの修正、機能の追加、全てのプラットフォームで JavaScript の動作を一貫させることに力を注いでいます。いつ 2.1 が公開できるかは分かりませんが、秋よりはずっと早くなるだろうと思いますよ ── 私たちは素早いですから。
JavaScript 1.0 [Netscape 1996d] は単純な動的言語語で、サポートされる機能には数値・文字列値・真偽値、ファーストクラスの関数、そしてオブジェクトというデータ型があった。構文的には Java と同じく C の仲間であり、制御フロー文は C から借用し、式の構文として C の数値演算子の大半を持っていた。また JavaScript 1.0 は組み込みの関数からなる小さなライブラリを持つ。通常 JavaScript 1.0 のソースコードは HTML ファイルに直接埋め込まれたものの、この組み込みのライブラリに含まれる eval 関数を使えば JavaScript の文字列値としてエンコードされた JavaScript ソースコードをパースして評価することもできた。JavaScript 1.0 は非常に小さな言語だった。JavaScript 1.0 が持たない機能の中で現代の JavaScript プログラマを驚かせるであろうものを 図 3 に示す。
- 個別の配列型
- 正規表現
undefinedに対するグローバルな束縛typeof,void,delete演算子do-while文try-catch-finally文- ネストされた関数宣言
- 関数の
callとapplyメソッド - プロトタイプベースの継承
- 循環型ガベージコレクション語
- 配列リテラル
- オブジェクトリテラル
===演算子in,instanceof演算子switch文- ラベルへの
break/continue - 関数式
- 関数のプロトタイププロパティ
- 組み込みのプロトタイプへのアクセス
- HTML の
<script>タグに対するsrc属性
1996 年の初めに「Atlas」の開発が始まった [Netscape 1996g]。Atlas は 1996 年 8 月に出荷されることになる Netscape Navigator 3.0 のコードネームである。Brendan Eich は 1995 年 8 月に行われた 2.0 に向けた機能凍結の時点で不完全だった機能や存在しなかった機能の開発を続けることができ、Navigator 3.0 に同梱された JavaScript 1.1 のリリースをもって初期の JavaScript の定義と開発が完了した。以降の節では JavaScript 1.0 および JavaScript 1.1 の言語設計を概観する。
3.1 JavaScript の構文
JavaScript 1.0 の構文はプログラミング言語 C [ANSI X3 1989] の文の構文を忠実に真似て作られており、加えて AWK語 [Aho et al 1988] から影響を受けた装飾もいくつか持っている。スクリプトは文 (statement) と宣言 (declaration) の並びである。C と異なり、JavaScript の文は関数の本体以外にも書くことができる。JavaScript 1.0 においてスクリプトのソースコードは <script> タグに囲まれて HTML ドキュメントに埋め込まれる。
JavaScript 1.0 が持つ C に影響された文には式文 (expression statement)、if による条件文、for と while による反復文、break, continue, return による非連続的なフロー制御、そして複数の文を {} で囲って一つの文であるかのように扱う文ブロック (statement block) がある。if, for, while は複合文1である。C に存在する do-while 文、switch 文、文ラベル、goto 文に対応する構文を JavaScript 1.0 は持たない。
C 風の基本的な文に加えて、JavaScript 1.0 にはオブジェクトというデータ型が持つプロパティにアクセスするための複合文を二つ持つ。一つ目は AWK に影響を受けた for-in 文で、オブジェクトのプロパティキー語を反復する。二つ目は with 文で、これを使うと指定されたオブジェクトのプロパティにその名前の変数が定義されたかのようにアクセスできる2。オブジェクトのプロパティは動的に追加できる (後のバージョンでは削除もできる) ので、with 文の本体から見える変数束縛語は実行の進行につれて変化する。
JavaScript の宣言は C や Java の宣言とはスタイルが異なる。JavaScript は動的型付け語である。さらに、宣言を示す構文的な接頭辞として型名を使うやり方を言語レベルで採用しない。その代わりJavaScript の宣言ではキーワードが接頭辞となる。 JavaScript 1.0 には function 宣言と var 宣言という二種類の宣言がある。function 宣言は AWK と同じ構文3を採用し、単一の呼び出し可能な関数の名前、仮パラメータ、本体の文を定義する。var 宣言は一つ以上の変数束縛を作成し、それらにデフォルト値を設定する (デフォルト値の設定は省略可能)。var 宣言は文として扱われ、文を期待する任意の文脈 (例えばブロック文の内部) に配置できる。JavaScript 1.0 と JavaScript 1.1 では function 宣言を配置できるのはスクリプトのトップレベルだけであり、function 宣言の中に入れ子にはできない。var 宣言は関数本体に配置でき、関数内の var 宣言で定義される変数はその関数にローカルとなる。
C と異なり、JavaScript 1.0 の文ブロック ({...}) は宣言のスコープを作成しない。例えば関数に含まれる文ブロックの中で var 宣言を行うと、関数の本体全体から見えるローカルな変数が作成される。また関数の外側にある var 宣言はグローバルなスコープ語を持つ。定義されていない変数名への代入 (つまりスコープに入っている function 宣言もしくは var 宣言が存在しないような変数名への代入) を行うと、その名前のグローバル変数が暗黙に宣言される。この振る舞いは数多くのエラーを引き起こす原因となった。宣言された変数の名前を打ち間違えたときに、警告やエラーが一切出ずに間違えた名前の変数が作成されてしまうためである。
伝統的な C の構文からの大きな飛躍の一つに、JavaScript における文末のセミコロンの扱いがある。C では文を分ける文字としてセミコロンが必須なのに対して、JavaScript では行末の有意な文字がセミコロンであるときは省略を可能にしている。この振る舞いの正確な規則は JavaScript 1.0 のドキュメントに含まれていない。また Netscape 2.0 Handbook では様々な JavaScript 文の形式を示すときにセミコロンを付けていない。この本には「一つの文を複数行に分けて書いても構わない。また複数の文を一行に書くこともできるが、そのときは文をセミコロンで分ける必要がある」とだけある [Netscape 1996d]。セミコロンを使わないコーディングスタイルは Netscape 2.0 Handbook に含まれる様々なサンプルコードでも使われている。サンプルコードの例を示す:
var a, x, y
var r = 10
with (Math) {
a = PI * r * r
x = r * cos(PI)
y = r * sin(PI/2)
}
セミコロンを使わずに JavaScript コードが書ける機能を自動的なセミコロンの挿入 (automatic semicolon insertion, ASI) と呼ぶ。ASI は現在でも JavaScript プログラマの間で議論の的になりやすい話題である。多くのプログラマはセミコロンを使わないスタイルのコードを書きたがるものの、ASI など誰も使わない方がいいと考えているプログラマもいる。
3.2 データ型と式
JavaScript 1.0 と JavaScript 1.1 は動的型付けの言語で、基礎的なデータ型として数値、文字列、真偽値、オブジェクト、関数の 5 つが存在する。ここで「動的型付け」とは実行時の型情報がデータ自身に関連付けられ、変数といったデータの入れ物には関連付けられないことを意味する。各種の操作がそれをサポートするデータにだけ適用されることは実行時の型検査が保証する。
真偽値、文字列値、数値は即値である。真偽値型は true と false という二つの値を持つ。文字列型の値は 8 ビット文字コードの不変な列から構成される。Unicode はサポートされない。数値型は IEEE 754 [IEEE 2008] が定める倍精度バイナリ 64 ビット浮動小数点数の全ての値から構成され、値 NaN の表現は正準な表現が一つだけ公開される。一部の数値演算では、符号無し 32 ビット整数あるいは 2 の補数表現の符号付き 32 ビット整数を使ったかのような結果が得られるように特別な処理が行われる。Mocha はそういった整数値に対する別種の表現を内部に持っていたものの、公式には数値型は一つしか存在しないとされた。
JavaScript 1.0 には有用なデータ値が存在しないことを表す特別な値が二つ存在する。まず、未初期化の変数は undefined という特別な値4に設定される。この値はオブジェクトが持っていないプロパティにアクセスしたときに返る値でもある。JavaScript 1.0 では未初期化の状態で宣言した変数にアクセスすることで undefined にアクセス可能だった。もう一つの null という値はオブジェクトの値が期待される文脈で「オブジェクトが存在しない」という事実を表現するために存在する。これは Java の値 null を真似たものであり、Java を使って実装されたオブジェクトと JavaScript の統合を容易にする。JavaScript の歴史全体を通じて、明らかに異なるにもかかわらず意味が似ているこの二つの値の存在は多くのプログラマを混乱させてきた。どちらをいつ使うべきかが分かりにくいからである。
JavaScript 1.0 の式の構文は C からコピーされ、ほぼ同じ演算子と同じ優先順位が使われた。C からコピーされなかった重要な式にはポインタと型に関連する演算子、そして単項 + 演算子がある。二項 + 演算子は数値の加算と文字列の連結の両方が行えるようオーバーロードされた。シフト演算子とビット論理演算子は 2 の補数表現の符号付き 32 ビット整数に対する演算を行い、必要な場合はオペランドを整数に切り捨ててから 32 ビットの値になるようモジュロを取る。その 32 ビット整数値に対して、例えば >> 演算子であれば符号拡張付きの算術右シフトが行われる。この他に JavaScript には Java から借用した >>> 演算子が追加されている。この演算子は符号無し右シフト (論理右シフト) を行う。
JavaScript 1.1 では delete, typeof, void 演算子が追加された。JavaScript 1.1 の delete 演算子はオペランドに受け取ったオブジェクトのプロパティあるいは変数の値をただ null に設定する。typeof 演算子はオペランドのプリミティブ型を識別する文字列を返す。typeof 演算子の返り値は "undefined", "object", "function", "string", "number" のいずれか、あるいはホストが提供するオブジェクトの種別を識別する実装定義の文字列と規定される。意外なことに、typeof null は "null" ではなく "object" を返す。これは全ての値がオブジェクトであり null も「オブジェクトがない」を表すオブジェクトに過ぎない Java に合わせた動作なのは間違いない。しかし Java は typeof 演算子に対応するものを持たず、未初期化の変数のデフォルト値を null で表す。Brendan Eich の記憶によると、typeof null の値が "object" になるのはオリジナルの Mocha の実装が持っていた漏れのある抽象化語の結果だという。実行時に値 null をエンコードするのに使われた内部タグがオブジェクトのものと同じだったので、特殊ケースのロジックを追加しないで typeof 演算子を実装すると typeof null が "object" を返したのである。この選択は JavaScript プログラマにとって大きな頭痛の種となってきた。アクセスするプロパティに値が設定されていることを最初に確認する処理はよく行われるが、プロパティに対する typeof が "object" を返すことを確認するだけではガードとして十分ではない。そのプロパティが null だとアクセスしたときに実行時エラーが発生してしまう。
void 演算子はオペランドを評価して undefined をただ返す。undefined には void 0 というイディオムを使ってアクセスされる。void 演算子はクリックされたときに JavaScript コードを実行する HTML ハイパーリンクの定義を簡単にするために導入された。例えば次のように使われる:
<a href="javascript:void usefulFunction()">Click to do something useful</a>
href 属性語の値には URL が期待され、javascript: はブラウザが認識する特別な URL プロトコルを表す。こうするとブラウザには、以降の文字列を JavaScript コードとして実行し、その結果を文字列に変換したものを通常の href が持つ URL を使ってフェッチされたレスポンスドキュメントとみなして扱うようにという指示が伝わる。ただし評価結果が undefined だとレスポンスドキュメントに対する処理は行われない。通常ウェブ開発者は <a> タグ内に JavaScript 式を入れるときはリンクがクリックされたときの評価で起こる効果だけを期待する (式の評価結果をレスポンスドキュメントとして利用することはない) ので、式の前に void 演算子を付けて式の評価より後の処理を抑制する。
式に関連する C と JavaScript の最も重要な違いは、JavaScript の演算子がオペランドを自身の演算が定義される型の値へと自動的に変換することである。この自動的な変換を型強制 (coerce) と呼ぶ。JavaScript 1.1 では設定可能な型強制の仕組みが追加され、任意のオブジェクトを数値型あるいは文字列型の値に型強制できるようになっている。JavaScript 1.1 の型強制規則を図 4 に示す。
| 変換先 | ||||||
| 関数 | オブジェクト | 数値 | 真偽値 | 文字列 | ||
| 変換元 | undefined |
エラー | null |
エラー | false |
"undefined" |
| 関数 | N/C | 関数オブジェクト | valueOf/エラー |
valueOf/エラー |
デコンパイル | |
| オブジェクト | N/C | |||||
(非 null) |
関数オブジェクト | valueOf/エラー |
valueOf/true |
toString/valueOf1 |
||
(null) |
エラー | 0 |
false |
"null" |
||
| 数値 | N/C | |||||
(0) |
エラー | null |
false |
"0" |
||
(非 0) |
エラー | Number |
true |
デフォルト | ||
NaN |
エラー | Number |
false2 |
"NaN" |
||
| (正の無限大) | エラー | Number |
true |
"+Infinity" |
||
| (負の無限大) | エラー | Number |
true |
"-Infinity" |
||
| 真偽値 | 真偽値 | N/C | ||||
(false) |
エラー | 0 |
"false" |
|||
(true) |
エラー | 1 |
"true" |
|||
| 文字列 | 文字列 | N/C | ||||
| (空文字列) | エラー | エラー3 | false |
|||
| (非空文字列) | エラー | 数値/エラー | true |
|||
- スラッシュで区切られた二つの値: JavaScript はまず左にある値を試し、もし成功しなかったら右にある値を使う。
- N/C: 変換は必要にならない (No Conversion Necessary)。
- デコンパイル: 関数の正準ソースからなる文字列
toString:toStringメソッドの呼び出し結果valueOf:valueOfメソッドの呼び出し結果 (変換先の値を返したときに限る)- 数値: 文字列が整数もしくは浮動小数点数のリテラルなら、その数値
valueOfが文字列を返さなかったら、通常のオブジェクトから文字列への変換が使われる。- Navigator 3.0 で実装された JavaScript 1.1 は
NaNをtrueに変換する。 - Navigator 3.0 で実装された JavaScript 1.1 は空文字列を
0に変換する。
3.3 オブジェクト
JavaScript 1.0 のオブジェクトは連想配列であり、その要素は「プロパティ」と呼ばれる。各プロパティはキー (key) と値 (value) を持ち、キーは文字列、値は任意の JavaScript データ型の値である。プロパティは動的に追加できる。JavaScript 1.0 と JavaScript 1.1 はオブジェクトからプロパティを削除する手段を提供しない。
あるプロパティのキー文字列が識別子としての構文規則に合致するなら、そのプロパティには obj.prop0 のようにドット記法を使ってアクセスできる。識別子としての構文規則に合致しないものを含む全てのプロパティには鍵括弧を使った記法でアクセスできる。この記法では鍵括弧で囲まれた式を評価した結果が文字列に変換され、その文字列がキーとして使われる。例えば n が 0 のとき obj["prop" + n] は obj.prop0 に等しい。存在しないプロパティに代入を行うと新しいプロパティが作成され、存在しないプロパティの値にアクセスすると通常は undefined が返る。ただし JavaScript 1.0 と JavaScript 1.1 では鍵括弧を使った記法で存在しないプロパティの値にアクセスし、そのプロパティキーが非負整数の文字列表現である場合には null が返る。
プロパティはデータの格納場所としてもオブジェクトの振る舞いを記述する場所としても利用できる。値が関数であるプロパティはオブジェクトのメソッドとして起動できる。メソッドとして起動された関数には動的に束縛されるキーワード this を通して呼び出しに使われたオブジェクトへのアクセスが与えられる (§3.7.4)。
オブジェクトの作成は組み込みの関数もしくはユーザー定義の関数に対して new 演算子を適用することで行う。この方式で使われることが意図されている関数は「コンストラクタ (constructor)」と呼ばれる。通常コンストラクタは新しく作成されるオブジェクトにプロパティを追加し、そのプロパティが新しいオブジェクトのデータ格納場所あるいはメソッドとなる。組み込みのコンストラクタ Object を使うと作成時点ではプロパティを一つも持たないオブジェクトを作成できる。Object コンストラクタとユーザー定義のコンストラクタ関数を使って新しいオブジェクトを作成するコードを図 5 に示す。
// Object コンストラクタを使う方法
var pt = new Object;
pt.x = 0;
pt.y = 0;
// 独自のコンストラクタを使う方法
function Point(x, y) {
this.x = x;
this.y = y;
}
var pt = new Point(0, 0)
Object で作成したオブジェクトに後から追加することもできるし、独自のコンストラクタ関数の中でオブジェクトを作るときに追加することもできる。
JavaScript 1.0 には組み込みの配列コンストラクタ Array もある。ただ Object コンストラクタで作ったオブジェクトと Array コンストラクタで作ったオブジェクトの目に見える違いは、デバッグするときにオブジェクトの表示が違う点しかない。JavaScript 1.0 において Array コンストラクタで作ったオブジェクトは length プロパティを持たない。
配列風な添え字アクセスの振る舞いはプロパティキーに整数値を持つプロパティを作成することで任意のオブジェクトに追加できる。そういったオブジェクトが整数でないキーを持つプロパティを保持しても構わない:
var a = new Object; // あるいは new Array
a[0] = "zero";
a[1] = "one";
a[2] = "two";
a.length = 3;
JavaScript 1.0 にはオブジェクトの継承という概念が存在せず、プログラムは新しいオブジェクトのそれぞれに全てのプロパティを個別に追加しなければならない。通常この処理はプログラムが利用するオブジェクトの「クラス」のそれぞれに対してコンストラクタ関数を定義することで行われる。Point という単純な抽象化を JavaScript 1.0 で定義するコードを図 6 に示す。この例で重要なのは次の点である:
- 各メソッドはグローバルに利用可能な関数として定義されなければならない。そういった関数には、他のクラス風抽象化に関連付くメソッドを定義するときに使われる関数と衝突しにくい名前 (
ptSum,ptDistance) を付ける必要がある。 - オブジェクトを構築するとき、各メソッドを表すオブジェクトプロパティの値を対応するグローバル関数に初期化しなければならない。
- メソッドの起動はグローバルな名前 (
ptDistance) ではなくオブジェクトに設定されたプロパティ名 (origin.distance) を使って行う。
// メソッドとして使うための関数を二つ定義する
function ptSum(pt2) {
return new Point(this.x+pt2.x, this.y+pt2.y)
}
function ptDistance(pt2) {
return Math.sqrt(Math.pow(pt2.x-this.x, 2) + Math.pow(pt2.y-this.y, 2));
}
// Point というコンストラクタを定義する
function Point(x,y) {
// 新しいオブジェクトのデータプロパティを作成、初期化する
this.x = x;
this.y = y;
// インスタンスオブジェクトごとにメソッドを追加する
this.sum = ptSum;
this.distance = ptDistance;
}
var origin = new Point(0,0); // Point オブジェクトを作成する
Point という抽象化の定義。それぞれのインスタンスオブジェクトが独自にメソッドプロパティを持つ。
JavaScript 1.1 ではコントラクタ関数のそれぞれにプロトタイプ語オブジェクトを関連付けられるようになったので、新しいインスタンスの全てにメソッドを表すプロパティを直接作る必要はなくなった。プロトタイプオブジェクトは関数オブジェクトの prototype という名前のプロパティを通じて設定する。JavaScript 1.1 を解説した JavaScript Guide [Netscape 1996e] はプロトタイプを「指定された型の全てのオブジェクトが共有するプロパティ」と説明している。この説明は曖昧であり、「あるコンストラクタが作成する全てのオブジェクトが共有するプロパティを持ったオブジェクト」などとした方が正確になる。
この共有の仕組みをこれ以上は説明しないが、プロトタイプオブジェクトの特徴を次に記しておく:
- あるオブジェクトのプロパティにアクセスするとき、アクセスしようとしている名前のプロパティがそのオブジェクトを作成したコンストラクタに関連付いたプロトタイプに定義されているなら、そのプロトタイプオブジェクトのプロパティに対するアクセスが起こる。
- プロトタイプオブジェクトに対してプロパティの追加や編集を行うと、そのプロトタイプが関連付いたコンストラクタを使って過去に作成した全てのオブジェクトがすぐに影響を受ける。
- オブジェクトのプロパティに値を代入したとき、そのオブジェクトを作成したコンストラクタ関数に関連付いたプロトタイプが同じ名前のプロパティを持っているなら、隠蔽語[^18]が起こる。
組み込みのオブジェクト Object.prototype が持つプロパティは全てのオブジェクトからアクセスできる。ただし、そのオブジェクトあるいはそのプロトタイプがプロパティを隠蔽する場合は異なる。
図 6 で使ったのと同じ Point という単純な抽象化を JavaScript 1.1 で定義するコードを 図 7 に示す。このコードではメソッドプロパティがプロトタイプオブジェクトに一度だけインストールされており、以前のようにインスタンスオブジェクトのそれぞれに何度もメソッドプロパティが追加されることはない。プロトタイプのプロパティを通してオブジェクトに提供されるプロパティを継承プロパティ語 (inherited property) と呼び、オブジェクトに対して直接定義されるプロパティを固有プロパティ語 (own property) と呼ぶ。固有プロパティは同じ名前の継承プロパティを隠蔽する。
// メソッドとして使うための関数を二つ定義する
function ptSum(pt2) {
return new Point(this.x+pt2.x, this.y+pt2.y)
}
function ptDistance(pt2) {
return Math.sqrt(Math.pow(pt2.x-this.x, 2) + Math.pow(pt2.y-this.y, 2));
}
// Point というコンストラクタを定義する
function Point(x,y) {
// 新しいオブジェクトのデータプロパティを作成、初期化する
this.x = x;
this.y = y;
}
// 共有のプロトタイプオブジェクトにメソッドを追加する
Point.prototype.sum = ptSum;
Point.prototype.distance = ptDistance;
// Point オブジェクトを作成する
var origin = new Point(0,0);
Point という抽象化の定義。インスタンスオブジェクトはメソッドプロパティを Point.prototype オブジェクトから継承し、各インスタンスがメソッドプロパティを持つことはない。
プロトタイプオブジェクトのプロパティは通常メソッドである。その場合、コンストラクタが提供するプロトタイプは C++ の vtable や Smalltalk の MethodDictionary と同じ役割を果たす ── プロトタイプは共通の振る舞いを特定のオブジェクトの集合に結び付ける。コンストラクタが実質的なクラスとなり、そのプロトタイプはクラスのインスタンスが共有するメソッドのコンテナとなる。これは JavaScript 1.1 のオブジェクトモデルの合理的な解釈の一つではあるが、これ以外にも合理的な解釈は存在する。
「プロトタイプ」という命名は Brendan Eich の念頭に他のオブジェクトモデルがあったことを示す明らかな証拠である。そのモデルの発想はプログラミング言語 Self [Ungar and Smith 1987] から来ている。Self において、新しいオブジェクトの作成は特定のカテゴリに属するオブジェクトのプロトタイプを表すオブジェクトを部分的にクローンすることで行われる。各クローンはプロトタイプを指す親リンク (parent link) を持ち、プロトタイプが全てのクローンに共通の機能を提供する。JavaScript 1.1 のオブジェクトモデルは Self のモデルの一種とみなせる。つまりプロトタイプオブジェクトに対するアクセスがコンストラクタ関数を通して間接的に行われ、new 演算子がプロトタイプを雛型として新しいインスタンスをクローンするというモデルである。クローンされたインスタンスはプロトタイプオブジェクトのプロパティを継承語するので、JavaScript プログラマの一部はこの継承の仕組みを「プロトタイプ継承語」と呼ぶ。また Java をはじめとした多くのオブジェクト指向言語で使われる継承のスタイルを指して「古典的継承語」という隠語めいた言葉を使う JavaScript プログラマもいる。
JavaScript 1.1 のドキュメント [Netscape 1996e] はこういったオブジェクトモデルを完全には説明していない。このドキュメントは Netscape と Sun による 1995 年のプレスリリースにあるマーケティングストーリーと話を合わせている。つまり JavaScript はオブジェクトの対話を行うスクリプトを書くための言語として位置付けられ、実際の抽象化 (クラス) の定義は Java で書かれるものとされた。ネイティブの JavaScript が持つオブジェクトの抽象化機能は目立たない副次的な要素であり、大部分はドキュメントされなかった。
3.4 関数オブジェクト
JavaScript 1.0 と JavaScript 1.1 において、関数の定義は名前と呼び出し可能な関数を作成する。JavaScript の関数はファーストクラスのオブジェクト値である。トップレベルの var 宣言と同様、function 宣言に与えられた名前はグローバル変数を定義する。その変数の値は関数オブジェクトであり、変数に代入したり、プロパティの値として設定したり、関数呼び出しの引数として渡したり、関数から返り値として返したりできる。関数はオブジェクトなので、関数に対するプロパティの定義が行える。関数オブジェクトにプロパティを追加できることが分かる例を示す:
function countedHello() {
alert("Hello, World!");
countedHello.callCount++; // この関数の callCount プロパティを 1 増やす
}
countedHello.callCount = 0; // 関数に callCount というカウンタを関連付け、初期化する
for (var i=0; i<5; i++) countedHello();
alert(countedHello.callCount); // 5 が表示される
関数は仮パラメータのリストと共に宣言されるが、このリストのサイズは関数を呼ぶときに渡すことができる引数の個数を制限しない。関数を呼ぶときに宣言された仮パラメータの個数より少ない引数を渡すと、対応する引数がないパラメータは undefined に設定される。関数に渡された引数が宣言された仮パラメータの個数より多いときは、余分な引数の評価は行われるもののパラメータの名前を通して利用可能にはならない。ただし関数の本体を実行している間は関数オブジェクトの arguments プロパティの値が呼び出された引数全てからなる配列風のオブジェクトになるので、対応するパラメータが存在しない引数があってもそこから利用できる。この仕組みにより、可変長引数リストを処理する関数が書けるようになる。
3.5 組み込みライブラリ
JavaScript 1.0 には組み込みの関数、オブジェクト、コンストラクタからなるライブラリが付属する。このライブラリは少数の汎用オブジェクト5と関数、およびそれより多いホスト特有のオブジェクトと関数を定義する。Netscape Navigator ではホストオブジェクト語が現在開かれている HTML ドキュメントの要素のモデルを提供した。こういった API は最終的にドキュメントオブジェクトモデル (Document Object Model, DOM) レベル 0 として知られるようになる [Koch 2003; Netscape 1996b]。Netscape Enterprise Server ではホストオブジェクトによってクライアント/サーバー通信、クライアントおよびサーバーのセッション状態の管理、ファイルとデータベースへのアクセスがサポートされた。このサーバー用ホストオブジェクトの設計が Netscape のサーバー製品を超えて採用されることはなかった。
JavaScript の初期の設計は主にブラウザプラットフォームのニーズによって形作られた。初期バージョンの JavaScript を説明した Netscape のドキュメントはホスト環境から独立するライブラリ要素とホストに依存するライブラリ要素を明示的に区別していない。ただ DOM をはじめとしたブラウザプラットフォーム API の設計、進化、標準化に関しては個別に歴史を紐解くに値する奥深いストーリーが存在する。本稿では JavaScript の全体的な設計に関連するときにのみブラウザ関連の問題に触れる。
JavaScript 1.0 には汎用なオブジェクトクラスが String と Date の二つしか存在しない。これとは別に Math というグローバルなシングルトンオブジェクトもあり、このオブジェクトはプロパティを通して数学関連の定数や関数を提供する。アクティブでないクラスや完全に実装されていないクラスに対するコンストラクタもいくつか存在し、アクセス方法を知っていれば JavaScript 1.0 でもアクセスできた。そういった機能の実装は JavaScript 1.1 で完了し、ドキュメントにも記載された。JavaScript 1.0 と JavaScript 1.1 で定義されるホスト非依存のクラスを図 8 に示す。
| ベースオブジェクト | 1.0 のプロパティ | 1.1 で追加されたプロパティ | |
|---|---|---|---|
| 1.0 | 1.1 | ||
| (グローバル関数) | eval, isNaN2, parseFloat2, parseInt2 |
||
Array |
Array |
join, reverse, sort, toString |
|
Boolean |
Boolean |
toString |
|
Date |
getDate, getDay, getHours, getMinutes, getMonth, getSeconds, GetTime, getTimezoneOffset, getYear, setDate, setHours, setMinutes, setMonth, setSeconds, setTime, setYear, toGMTString, toLocaleString, Date.parse, Date.UTC |
toString |
|
Function |
Function |
prototype, toString |
|
Math |
E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2, abs, acos, asin, atan, ceil, cos, exp, floor, log, max, min, pow, random1, round, sin, sqrt, tan |
||
Object |
constructor, eval, toString, valueOf |
||
Number |
Number |
toString, Number.NaN, Number.MAX_VALUE, Number.MIN_VALUE, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY |
|
| (文字列値) | length |
||
String |
charAt4, indexOf, lastIndexOf, splitsubstring, toLowerCase, toUpperCase (これ以外に文字列値を HTML でラップするメソッドが 13 個) |
split, toString, valueOf |
|
- 1.0 では Unix プラットフォームでのみ利用可能。
- 1.0 ではホストオペレーティングシステムによって動作が異なる。
- 1.0 に存在したものの、バグがあったり壊れていたりした。
- 1.0 でこれらのメソッドは文字列値のプロパティであるように見える。1.1 では
String.prototypeのプロパティとなった。
String クラスは length プロパティと 6 個の汎用メソッドを提供する。これらのメソッドは不変文字列値に対する操作を行い、必要な場合は新しい文字列値を返す。加えて JavaScript 1.0 の String クラスには文字列値を様々な HTML タグで包むメソッドが 13 個存在する。これは JavaScript 1.0 と JavaScript 1.1 においてホスト依存の機能と汎用な機能の間の境界があやふやだったことを示す例と言える。JavaScript 1.0 はグローバルな String コンストラクタ関数を提供せず、全ての文字列値は文字列リテラル、演算子、組み込み関数のいずれかを通して作成される。JavaScript 1.1 はグローバルな String コンストラクタと split メソッドを追加した。
Date クラスは日付と時刻を表現するために使われる。JavaScript 1.0 の Date は Java 1.0 の java.util.Date [Gosling et al. 1996] をそのまま移植したものであり、バグなどもそのままとなっている。エンコーディングに関する細かな約束事、例えば 1970 年 1 月 1 日 の 00:00:00 GMT を基準としてミリ秒の精度で時刻を表現すること、あるいは外部とのやり取りで月を 0 から 11 の整数で表すことも同じで、Java の設計に存在した 2000 年の曖昧さも残っている。この設計の選択は Java との相互運用性の要件によって動機付けられた。Java に存在するメソッドの中で equals, before, after だけは除外されたが、これは必要なかったためである。JavaScript には自動的な型強制があるために数値の比較演算子を Date オブジェクト対して直接利用でき、個別のメソッドは必要にならない。
Object を除くと、Date は JavaScript 1.0 で利用できた唯一の組み込みコンストラクタ関数である。また Date はクラスのインスタンス用でないメソッドがコンストラクタオブジェクトを通して公開されていた唯一のクラスでもある。ブラウザ固有のクラスはどれもコンストラクタ関数を公開しなかった。
組み込みライブラリのオブジェクトおよびホストが提供するオブジェクトが持つプロパティの一部は、JavaScript プログラマが定義するプロパティが持てない特徴を持っていた。例えば、これらのオブジェクトのメソッドプロパティは for-in 文で列挙されない。delete 演算子が無視するプロパティや、読み込み専用の値を持つプロパティも存在する。また一部のプロパティに対してアクセスや改変を行うと、外から見える副作用を持った特別な振る舞いが起こる。
JavaScript 1.1 では使いものになる Array クラスが追加された。Array コンストラクタはゼロ始まりの整数を添え字とする不均一ベクトルとして使われることを意図したオブジェクトを作成する。配列の要素はオブジェクトであり、整数の添え字の文字列表現をキーに持つプロパティの値として表現される。配列オブジェクトは length プロパティを持ち、この値は最初コンストラクタによって設定される。length プロパティの値は現在の length の値と同じかそれより大きい位置にアクセスが起こるたびに更新されるので、Array オブジェクトの要素数は動的に増やすことができる。
3.6 実行モデル
Netscape 2 とそれ以降のブラウザにおいて、一つの HTML ウェブページは複数の <script> 要素を保持できる。ページが読み込まれるとき、まっさらな JavaScript 実行環境とグローバルコンテキストがその HTML ドキュメントに対して作成される。グローバルコンテキストにはスクリプトによって定義されるグローバルな変数と関数、そしてグローバルオブジェクトが含まれる。グローバルオブジェクトとは JavaScript とホスト環境が提供する組み込みの関数および変数の名前、そしてスクリプトが定義するグローバルな変数と関数の名前がプロパティキーとなるようなオブジェクトである。
Netscape 2 において、それぞれの <script> 要素に含まれる JavaScript コードはそのページの HTML ファイルにある順番通りにパースと評価が行われる。後のブラウザでは <script> 要素に遅延評価を許可するタグを付けることができ、この機能を使うと JavaScript コードをネットワークから取得するのを待っている間にブラウザは HTML の処理を進められるようになる。いずれの場合でもブラウザはスクリプトを一つずつ実行する。一つの HTML に含まれる全てのスクリプトは通常同じグローバルオブジェクトを共有し、あるスクリプトが作成するグローバルな変数と関数は以降の全てのスクリプトから参照できる。それぞれのスクリプトは必ず最後まで実行され、中断あるいは停止されることはない。初期のブラウザが持っていたこの特徴は JavaScript の基礎的な原則となった。スクリプトは不可分な実行単位であり、一度開始されると完了するまで実行される。並列実行は起こらないので、スクリプトの内部で他のスクリプトの実行からの干渉を気にする必要はない。
Netscape 2 はウェブページのフレーム6という概念も導入した。フレームは個別の HTML ドキュメントを読み込むために存在するウェブページ内の領域である。一つのページに含まれる全てのフレームは同じ実行環境を共有するものの、各フレームはその環境下で個別のグローバルコンテキストを持つ。つまり異なるフレームで読み込まれたスクリプトは異なるグローバルオブジェクト、異なる組み込み関数および定数、そして異なるグローバル変数および関数を持つ。ただしグローバルコンテキストはアドレス空間ではない。JavaScript 実行環境はオブジェクトを格納するための単一のアドレス空間を持っており、それが環境内の全てのフレームで共有される。オブジェクトが単一のアドレス空間に存在するために、異なるフレームに属する JavaScript コードの間でオブジェクトの参照をやり取りして異なるグローバルコンテキストに属するオブジェクトを混ぜ合わせることはできる。これを行うと驚くような振る舞いが得られることもある。図 9 に示した JavaScript 1.1 コードの例を見てほしい。各フレームは異なる Object コンストラクタと Object.prototype を持ち、Object.prototype は Object によって作成される全てのオブジェクトが継承するプロパティを持つ。しかし、あるフレームの Object.prototype にプロパティを追加したとしても、他のフレームの Object コンストラクタで作成されたオブジェクトはそのプロパティを継承しない。
// alien は他のフレームで new Object() を実行することで作成されたオブジェクトへの参照
var alien = createNewObjectInADifferentFrame();
var native = new Object(); // 現在のフレームでオブジェクトを作成する
Object.prototype.sharedProperty = "each frame has distinct built-ins";
alert(native.sharedProperty); // 「each frame has distinct built-ins」と表示される
alert(alien.sharedProperty); // undefined が表示される
対話的な JavaScript ウェブページはイベント駆動のアプリケーションであり、イベントループはブラウザによって提供される。HyperCard [Apple Computer 1988] から着想を得て、Brendan Eich はイベントという概念を Netscape 2 DOM [Netscape 1996c] の初期設計に取り入れた。当初イベントは主にユーザーからの操作によって起動されていたものの、モダンなブラウザには様々な種類のイベントが存在し、ユーザーがきっかけとなるのはその一部でしかない。
ウェブページが定義するスクリプトの実行が全て終了すると、そのページに対する JavaScript 環境はアクティブなままイベントの発生を待機する。ブラウザが提供するオブジェクトにはイベントハンドラを関連付けることができ、例えば様々な DOM オブジェクトにも関連付けは行える。イベントハンドラは単なる JavaScript 関数であり、イベントの発生に対する応答の一部として呼び出される。ブラウザオブジェクトの特定のプロパティに関数を代入すると、そのプロパティに関連付いたイベントに対するハンドラがその関数となる。例えばポインティングデバイスでクリック可能な要素に対応するオブジェクトには onclick プロパティを設定できる。JavaScript イベントハンドラは HTML 要素の中でコードスニペットを使って直接定義することもできる。この例を示す:
<button onclick="doSomethingWhenClicked()">Click me</button>
この HTML 要素を処理するとき、ブラウザは新しく JavaScript 関数を作成してボタンオブジェクトの onclick プロパティの値にそれを代入する。HTML の onclick 属性にあるコードスニペットは作成される関数の本体となる。JavaScript イベントハンドラが存在するイベントが発生すると、そのイベントは実行待ちイベントが入ったプールに移される。実行中の JavaScript コードがないとき、ブラウザはイベントプールから実行待ちイベントを取り出して関連付いた関数を呼び出す。スクリプトと同様に、イベントハンドラの関数は終了するまで実行される。
3.7 奇妙な振る舞いとバグ
JavaScript には珍しい機能や意外な機能が存在する。意図的なものもあれば、オリジナルの Mocha を作る 10 日間のスプリントで行われた早急な設計判断の結果であるものもある。また JavaScript 1.0 にはバグや不完全に実装された機能がある。
3.7.1 重複する宣言
JavaScript は同一のスコープで同じ名前を複数回宣言することを許している。関数内で行われる同じ名前の宣言は全て単一の束縛に対応し、関数全体から見えるようになる。例えば、次の関数定義は正当である:
function f(x, x) { // x は二つ目のパラメータを表す (一つ目の x は無視される)
var x; // 二つ目のパラメータと同じ束縛を指す
for (var x in obj) { // 二つ目のパラメータと同じ束縛を指す
var x=1, x=2; // 二つ目のパラメータと同じ束縛を指す
}
var x=3; // 二つ目のパラメータと同じ束縛を指す
}
この関数 f の内部にある var 宣言で定義される変数は全て同じ変数束縛を指しており、それは関数の第二パラメータの束縛である。関数の仮パラメータリストに同じ名前が含まれていても構わない。var 宣言で定義される全ての変数は関数本体の実行に先立って undefined に初期化されるが、そのときパラメータと同名の var 変数だけは例外となる。そういった変数の初期値は同名のパラメータに渡された引数と同じになる。var 宣言の初期化処理は重複する宣言を含めて初期化された変数への代入と同じ意味論を持つ。つまり初期化処理は関数本体の通常の実行の流れが宣言の場所に達したときに実行される。
スクリプト内に同じ名前を持った function 宣言が複数存在することもある。この場合、最後の function 宣言がスクリプトの先頭に巻き上げられ、宣言された名前のグローバル変数がそれを使って初期化される。同じ名前を持つ他の function 宣言は無視される。同じ名前を定義するグローバルな function 宣言とグローバルな var 宣言がどちらも存在するときは全ての宣言が同じ変数を操作するものとされ、実行の流れが初期値を持った var 宣言に達したときに、その初期値で関数が上書きされる。
3.7.2 自動的な型強制と == 演算子
自動的な型強制 (coercion) は JavaScript を簡単なスクリプト言語として採用するときの参入障壁を低くすることを目的として導入された。しかし JavaScript が汎用的な言語へ進化するにつれ、型強制は大量の混乱とバグを引き起こした。これは == 演算子に関して特に当てはまる。問題のある型強制の中には、JavaScript と HTTP/HTML の統合を簡単にすることを望んだアルファユーザーへの返答として最初の 10 日間のスプリントの後に追加されたものがある。例えば Netscape 内部のユーザーからは HTTP ステータスコードを含んだ文字列 (例えば "404") と数値 (例えば 404) を == で比較できるようにしてほしいという要望があった。また数値を比較する文脈で空文字列を 0 に型強制することで、HTML フォームの空フィールドに対してデフォルト値を提供してほしいという意見もあった。こういった型強制規則を追加した結果として、「1 == '1' かつ 1 == '1.0'、しかし '1' != '1.0'」などの奇妙な関係が生じることになった。
JavaScript 1.0 は if 文の条件節で = 演算子を == 演算子と同一に扱う。この例を示す:
// 二つの文は同一
if (a = 0) alert("true");
if (a == 0) alert("true");
3.7.3 32 ビット算術
JavaScript のビット論理演算子は IEEE 754 の倍精度浮動小数点数にエンコードされた 32 ビットの整数値 (符号無しまたは符号付き) に対して操作を行う。ビット演算子の評価では最初に数値を整数に切り捨ててからモジュロを取ることでオペランドを 2 の補数表現の 32 ビット整数に変換し、それからビット演算を行う。例えば数値 x があるとき、x を 32 ビットで表せる符号付き整数に変換した値を表す式はビット OR 演算子 | を使って x|0 と書ける。32 ビットの符号付き加算を行うイディオムは次のように書ける:
function int32bitAdd(x, y) {
// 結果を 32 ビットに切り捨てる加算
return ((x|0) + (y|0))|0
}
|0 の代わりに符号無し右シフト演算子を使って >>>0 とすると、同じパターンの式で符号無しの算術を行える。
3.7.4 キーワード this
全ての関数は暗黙に this というパラメータを持つ。関数がメソッドとして呼ばれると、この this パラメータは呼び出されたメソッドへのアクセスに使われたオブジェクトに設定される。これは多くのオブジェクト指向言語における this (あるいは self) と同じ意味である。ただ JavaScript ではオブジェクトに結び付くメソッドと独立した関数の両方を同じ形式の宣言で定義するようになっており、この仕様は多くのプログラマに混乱とバグを引き起こしている。
関数がオブジェクトを伴わずに直接呼ばれるとき、this は暗黙にグローバルオブジェクトへと設定される。グローバルオブジェクトのプロパティにはプログラムのグローバル変数が全て含まれるので、直接呼び出された関数で this のプロパティを参照することはグローバル変数を直接参照するのと同じことになる。this の扱いが関数の呼び出し方によって変化するために、同じ this の参照であっても呼び出しによって異なる意味を持つことがあり得る。この例を示す:
function setX(value) {this.x=value}
var obj = new Object;
obj.setX = setX; // setX を obj のメソッドとしてインストールする
obj.setX(42); // setX をメソッドとして呼び出す
alert(obj.x); // 42 が表示される
setX(84); // setX を直接呼び出す
alert(x); // グローバル変数 x が参照され、84 が表示される
alert(obj.x); // 42 が表示される
さらに分かりにくいことに、一部の HTML 構文は JavaScript のコード片を暗黙に関数へと変形し、それをメソッドとして起動する。例として次のコードを考える:
<button name="B" onclick="alert(this.name + ' clicked')">Click me</button>
このボタンに対するイベントハンドラが実行されると、このボタンを表す JavaScript オブジェクトの onclick メソッドが起動される。そのため this はボタンオブジェクトを指し、this.name はボタンオブジェクトの name 属性を取得する。
3.7.5 引数オブジェクト
関数の実行中、仮パラメータとは別に引数オブジェクト (arguments object) と呼ばれるオブジェクトが利用可能になる ── 関数オブジェクトの arguments プロパティが引数オブジェクトとなり、arguments の数値をキーとするプロパティと関数の仮パラメータの間に動的なマッピングが構築される。arguments のプロパティを変更すると対応する仮パラメータの値も変更され、逆に仮パラメータを変更すると対応する arguments のプロパティも変化する:
f(1,2);
function f(argA, argB) {
alert(argA); // 1 が表示される
alert(f.arguments[0]); // 1 が表示される
f.arguments[0] = "one";
alert(argA); // one が表示される
argB = "two";
alert(f.arguments[1]); // two が表示される
alert(f.arguments.argB); // two が表示される
}
上のコードの最後にあるように、仮パラメータへのアクセスは引数オブジェクトに対する仮パラメータの名前をプロパティキーとしたアクセスでも行える。
概念上は、関数が呼び出されたとき、その呼び出しを表す新しい引数オブジェクトが作られて関数オブジェクトの arguments プロパティに設定される。ただ JavaScript 1.0 と JavaScript 1.1 では関数オブジェクトとその arguments プロパティの値が同じだった:
function f(a,b) {
if (f==f.arguments) alert("f and f.arguments are the same object")
}
if (f.arguments==null) alert("but only while a call to f is active")
理想的には、関数の arguments オブジェクトに対するアクセスは関数本体内でのみ行えるのが望ましい。これは関数の呼び出しから返るときに arguments プロパティを自動的に null に設定することで部分的に達成される。ただ、例えば f1 と f2 という二つの関数があって、f1 が f2 を呼び出すとする。このとき f2 から f1.arguments を評価すると f1 の引数にアクセスできてしまう。
引数オブジェクトには caller というプロパティも存在する。caller プロパティの値は現在の関数呼び出しを起動した関数オブジェクトであり、もし現在の呼び出しが一番外側の関数呼び出しなら null となる。caller と arguments を使うことで任意の関数は自身が載っている現在のコールスタックの引数を調べることができ、さらにコールスタック内の関数が持つ仮パラメータの値を変更することさえ可能となる。caller プロパティには arguments を経由せずに関数オブジェクトから直接アクセスすることもできる。
3.7.6 整数プロパティキーの特別扱い
JavaScript 1.0 では整数のキーを使った鍵括弧記法に特別な意味論が存在した。一部の場合で、鍵括弧内に整数のキーを入れるとオブジェクトのプロパティに対する作成順のアクセスとなる。このプロパティの作成順序を使ったアクセスは、整数 n をキーに持つプロパティがオブジェクトに存在せず、かつ n がオブジェクトの持つプロパティの総個数より小さい場合に発生する。この条件が成り立つと、そのオブジェクトで n 番目に作られたプロパティへのアクセスが起きる。この例を示す:
var a = new Object; // あるいは new Array
a[0] = "zero";
a[1] = "one";
a.p1 = "two";
alert(a[2]); // two が表示される
a[2] = "2";
alert(a.p1); // 2 が表示される
この鍵括弧記法の特別扱いは JavaScript 1.1 で削除された。
3.7.7 プリミティブ値のプロパティ
JavaScript 1.0 で数値と真偽値はプロパティを持たず、プロパティに対してアクセスあるいは代入を行うとエラーメッセージが表示される。文字列値はプロパティに関して基本的にオブジェクトであるかのように振る舞うものの、全ての文字列値は共通のプロパティの集合を共有し、さらに読み込み専用の length プロパティを持つ。この例を示す:
"xyz".prop = 42; // 全ての文字列の prop プロパティが 42 に設定される
alert("xyz".prop); // 42 が表示される
alert("abc".prop); // 42 が表示される
JavaScript 1.1 で数値、真偽値、文字列値のプロパティに対してアクセスまたは代入を行うと、組み込みの Number, Boolean, String コンストラクタのいずれかを使ってラッパーオブジェクト (wrapper object) が暗黙に作成される。これらの値に対するプロパティのアクセスと代入はラッパーオブジェクトに対して行われ、多くの場合で組み込みのプロトタイプから継承されたプロパティへの操作となる。valueOf メソッドと toString メソッドを自動的に起動することで行われる型強制では、多くの状況でラッパーオブジェクトをプリミティブ値かのように扱える。代入によってラッパーオブジェクトに新しくプロパティを追加することはできるものの、暗黙に作成されるラッパーオブジェクトは代入の直後にアクセスできなくなることが多い。この例を示す:
"xyz".prop = 42; // String を使ってラッパーオブジェクトが作成され、
// その prop プロパティが 42 に設定される
alert("xyz".prop); // 暗黙にラッパーオブジェクトがもう一つ作成される
// undefined が表示される
var abc = new String("abc"); // ラッパーオブジェクトを明示的に作成する
alert(abc+"xyz"); // ラッパーオブジェクトが暗黙に文字列に変換される
// abcxyz が表示される
abc.prop = 42; // ラッパーオブジェクトにプロパティを作成する
alert(abc.prop); // 42 が表示される
3.7.8 JavaScript 内部の HTML コメント
Netscape 2 に含まれる JavaScript には、HTML の <script> 要素に対する Netscape 1 と Mosaic の動作に関係する潜在的な相互運用性の問題が一つあった。これらの古い、しかし広く使われるブラウザは、<script> 要素の本体 ── JavaScript ソースコード ── をウェブページのテキストとして表示してしまうのである。この問題は古いブラウザでスクリプトの本体を HTML のコメントで囲むことで回避できる7。この例を示す:
<script>
<!-- スクリプトの本体を囲む HTML コメント
alert("this is a message from JavaScript"); // 古いブラウザでは見えない
// 次の行が HTML コメントの終わりを表す
-->
</script>
このコーディングパターンを使うと Netscape 1 と Mosaic の HTML パーサーはスクリプトの本体全体を HTML コメントと認識するので、これらのブラウザでスクリプトの本体は表示されない。しかし HTML コメントの区切りが JavaScript として構文的に正当でないために、最初に実装された Mocha でこのパターンを使うと本体のパース (そして実行) が停止してしまっていた。この問題を解決するために、Brendan Eich は JavaScript 1.0 で <!-- が // と同様の行コメントの始まりとして認識されるようにした。一方で彼は --> を JavaScript のコメントの区切りとして認識するようにはしなかった。このパターンを行うには --> の前に // を配置すれば十分だったためである。後方相互運用性を持ったスクリプトは次のように書ける:
<script>
<!-- これは古いブラウザにおける HTML のコメントであり、JS の行コメントでもある
alert("this is a message from JavaScript"); // 古いブラウザでは見えない
// 次の行は HTML コメントの終わりであり、JS の行コメントでもある
//-->
</script>
<!-- コメントは JavaScript の公式の構文としてドキュメントはされていなかったものの、ウェブ開発者によって利用され、他の JavaScript 実装もサポートした。この結果 <!-- は事実上の Web Reality語 の一部となっていた。それから 20 年の歳月を要したものの、2015 年に <!-- コメントは ECMAScript 規格に取り込まれた ── 最終的には Web Reality が必ず勝つのである。
-
複合文 (compound statement) は構文的な構成要素として入れ子になった文を内部に持つ。多くの複合文は内部に一つだけ文を持ち、その場合は内部の文を複合文の本体 (body) と呼ぶ。 ↩︎
-
with文は Mocha が作成された 10 日間のスプリントの後、Netscape の LiveWire チームから要望があって追加された。 ↩︎ -
return文の構文と意味論を含む。 ↩︎ -
この値に直接アクセスするための名前が JavaScript 1.0 に存在していなかったので、本節ではこの値を undefined と斜体で表記する。 ↩︎
-
JavaScript ではオブジェクトを抽象化する仕組みに正式な名前が付いていないために、JavaScript ライブラリがサポートする特定の種類のオブジェクトの集合について話をするのが難しい。JavaScript のドキュメントはそういった抽象化を指して「型」「オブジェクト」「コンストラクタ」「クラス」など様々な単語を使っている。本稿では以降、何らかの表現やメソッドを共有する JavaScript オブジェクトの集合の定義について話すとき (実際の定義の形式に関わらず) 大文字の「Class」を用いる。[訳注: 翻訳では「Class」を「クラス」とした。] ↩︎
-
オリジナルの HTML にあった
<frame>タグは廃止されており、現在では<iframe>タグに置き換わっている。本節で説明される意味論は両方の要素に共通する。 ↩︎ -
ただしスクリプトの本体は
>演算子と--演算子を含んでいてはいけない。この二つの文字列は HTML コメントとして不当であるため。 ↩︎