はじめに
JavaScriptはオブジェクト指向言語です。従って、そのプログラミングは、オブジェクトの生成やプロパティの参照、メソッドの実行などを組み合わせる作業と言えます。つまり、オブジェクトを上手く扱うことができなければ、JavaScriptの良いコードは書けません。また、JavaScriptにおけるオブジェクトの考え方は、JavaやC++とは根本的に違っています。
そこで、この記事では、JavaScriptにおけるオブジェクトの基本的な性質について見ていくと共に、JavaやC++といった他のオブジェクト指向言語との違い、JavaScriptにおけるオブジェクトの扱い方などを解説していきたいと思います。
なお、この記事はJavaScriptの解説ですが、その内容は、標準仕様のECMAScriptで扱われる範囲に基づいています。従って、同じくECMAScriptを元にしている言語(JScript、ActionScript)でも通じる内容になっています。
対象読者
プログラミングの基本的な知識、ならびにオブジェクトやメソッドと言った基礎的な概念については、ここでは解説しません。最低限、オブジェクト指向プログラミングについて理解をしている人を対象としています。
この記事は、主に以下のような人を対象としています。
- サーバサイドJavaを行っていて、JSPなどでJavaScriptの知識が必要になった人
- JavaScriptをより深く使いこなしたい人
- オブジェクト指向プログラミングを別のアプローチから捉えなおしてみたい人
- JavaScriptでクラスを作る方法は知っているが、何故そのように書くとクラスを作れるのかわからない人
必要な環境
ECMAScript 3rd editionの実装系。例えば、下記のような環境。
- Microsoft Internet Explorer 5.5以上
- Firefox 1.0以上
目次
「動的にメンバを定義する」ということ
JavaやC++といった言語では、オブジェクトが持つメンバの数は、そのオブジェクトが生成された時から消滅するまで常に変わりません。Javaにおけるメンバはクラスによって定義され、オブジェクトはクラスの定義から外れることができないのです(List1.1)。
class MyClass { int field_1 = 0; } public static void main(String[] args) { MyClass obj = new MyClass(); // クラスで定義しているメンバにはアクセス可能。 obj.field_1 = 10; // エラー。クラスで定義していないメンバは持てない。 obj.field_2 = 10; }
これに対し、JavaScriptにおけるオブジェクトのメンバ数は可変です。JavaScriptでは、クラスという言語機構は存在しません(*1)。オブジェクトのメンバは、クラスではなくそのオブジェクト自身が定義します。つまり、各オブジェクトが自由にメンバを追加したり削除したりすることができるのです。
では、そのメンバの定義はどのように行うのでしょうか。
これは非常に簡単で、定義したいメンバに代入を行うだけです。JavaScriptの代入式は、左辺のメンバが既に定義されていたら上書きして代入を行い、まだ定義されていなければ、そのメンバを定義し初期値に右辺の値を設定します(List1.2)。
var obj = new Object(); alert(obj.field); // 未定義。未定義の場合 undefined と評価される。 obj.field = 10; // 代入により field というメンバが定義された。 alert(obj.field); // 10 と評価される。
つまり、オブジェクトに対し代入を行っていくことで、そのオブジェクトのメンバを追加していくことができるわけです。
次にメンバの削除について見てみましょう。定義したメンバは、delete
演算子を用いることで削除することができます(List1.3)。
var obj = new Object(); obj.field = 10; alert(obj.field); // 10 と評価される。 delete obj.field; // delete 演算子により fieldプロパティが削除される。 alert(obj.field); // 削除され未定義となったため undefined となる。
以上のように、JavaScriptではプログラム中でオブジェクトのメンバをコントロールすることができます。プログラムのある時点では、二つしかメンバを持たないオブジェクトが、別のある時点では三つのメンバを持つことができる。このように動的にメンバを定義できることが、JavaやC++などと最も異なり、かつJavaScriptにおいて最も基本的なオブジェクトの性質になるわけです。
メンバのアクセスには二通りの方法がある
JavaScriptにおいて、メンバのアクセスには二通りの書き方があります。
obj.member
のようにドットを用いて識別子を直接記述するドット記法と、obj["member"]
のように識別子に文字列を用いる括弧記法です。これらは全く同じことを表しています。
前者は通常の方法なので特に問題はないでしょう。ここでは後者について注目してみたいと思います。
後者の括弧記法は、識別子をプログラム上の文字列で扱うための記述法です。識別子の内容を表す文字列を示せばよいので、文字列リテラルに限らず、変数やメソッドの戻り値などでも構いません。従って、以下のようなコードも問題なく成り立ちます(List1.4)。
var obj = new Object(); obj.member = "hoge"; var str = "member"; alert(obj.member == obj[str]); // true
この括弧記法を応用することで、ただのオブジェクトを連想配列のように扱うことができます。特にECMA262-3からは、日本語文字を含む多数のUnicode文字も識別子として使用できるようになりました(*2)。これにより、特に複雑なメソッドが必要ない限り、大概の連想配列なら通常のオブジェクトで十分に事足ります。
for inループ
オブジェクトを連想配列として扱った場合、その連想配列が持つ全ての要素に対してアクセスしたい、即ち、そのオブジェクトが持つ全てのメンバに対してアクセスしたい、という場合があります。JavaScriptでは、その要求を満たすためにfor in
という制御構造が提供されています。
これは、あるオブジェクトに対しそのオブジェクトが持つメンバが尽きるまでループを繰り返す、というもので、その構文はfor(ループカウンタinオブジェクト) ステート
という形になります。
オブジェクトと書かれた部分に、メンバを列挙したいオブジェクトを指定すると、ループ中では、ループカウンタ変数にそのメンバの識別子が代入されます。従って、括弧記法を用いることで、オブジェクトのメンバにアクセスすることが可能となります(List 1.5)。
var obj = new Object(); obj.foo = "foo"; obj.bar = ... 以下メンバ定義が続く。 for (var i in obj) { alert(i); // ループカウンタにメンバ名が入る。 alert(obj[i]); // 括弧記法でメンバを参照できる。 }
いわゆるPerlやPHPのfor_each()
と言えば、理解しやすいでしょうか。
プロパティの属性
さて、前節では、オブジェクトのメンバを列挙するfor in
について解説しました。そこで次のコード(List 1.6)を見てください。
// ソース var str = new String("This is a string."); alert(str.length); // 実行結果 17
文字列String
のオブジェクトを作成し、そのlength
プロパティを参照している簡単なプログラムです。String
のlength
プロパティは、その文字列の文字数を表すプロパティなので、"This is a string." の場合、17と評価されます。
続いて、次のコード(List 1.7)も見てください。
var str = new String("This is a string."); str.prop1 = "Property"; // prop1 というプロパティを定義する。 str.prop2 = 256; // prop2 というプロパティを定義する。 for (var i in str) { alert(i + " = " + str[i]); }
List 1.6と同じように文字列オブジェクトを作成し、そのオブジェクトにプロパティを定義して、for in
でそのオブジェクトのメンバを全部書き出すプログラムです。さて、ではこのプログラムを実行したら、どういう結果が得られるでしょうか?
実際にList 1.7を実行するとList 1.8の結果になります。
prop1 = Property prop2 = 256
さて、ここで注目して欲しいのは、List 1.8でlength
というプロパティが出力されていないことです。List 1.6から、文字列オブジェクトがlength
というプロパティを持つことは確実です。しかし、for in
でメンバを列挙してもlength
プロパティは見つかりませんでした。これはいったい何故でしょうか。
実はこれは、文字列オブジェクトのlength
プロパティがDontEnum
という属性を持っているからなのです。このDontEnum
属性を持つプロパティはfor in
ループで列挙されなくなります。
このように、オブジェクトのプロパティには属性というものが存在します。属性を持つプロパティは、その所有する属性に定義された性質を持つことになるのです。
属性には、上記のfor in
で列挙されないDontEnum
の他に、代入処理が無視されるReadOnly
、delete
演算子での削除処理が無視されるDontDelete
、直にアクセスできないInternal
があります。
これらプロパティの属性は、基本的にはプログラマがコントロールすることができません(*3)。つまり、プログラマが定義したプロパティにDontDelete
を与えたり、文字列オブジェクトのlength
からDontEnum
を外したり、などということはできません。
従って、これらの属性を持つプロパティというのは、基本的に組込みのオブジェクトが持つと言うことになります。
まとめ
今回は、以下のようなオブジェクトの基本的な性質とその操作の方法を学びました。
- オブジェクトのメンバは、プログラム中で動的に追加・削除できる。
- メンバのアクセスには、ドット記法と括弧記法の2通りがある。
for in
というメンバを列挙する制御構造がある。- プロパティは、属性とよばれる性質を持つことがある。
次回は、JavaScript関数の基本について解説します。
参考資料
本稿は、Starry Night 『JavaScript講座』にて公開したものです。連載を再開するにあたり、再編集を行いCodezineに寄稿いたしました。
- Ecma International 『Standard ECMA-262』
- 『Under Translation of ECMA-262 3rd Edition』