Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

JavaScriptにおけるオブジェクトの基本的性質

JavaScriptによるオブジェクト指向プログラミング 第1回

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/12/01 14:00

このシリーズは、JavaScript(ECMAScript)における概念と言語構造の理解を助けるためのドキュメントです。第1回目の本稿では、JavaScript(ECMAScript)におけるオブジェクトの基本的な性質について解説します。

はじめに

 JavaScriptはオブジェクト指向言語です。従って、そのプログラミングは、オブジェクトの生成やプロパティの参照、メソッドの実行などを組み合わせる作業と言えます。つまり、オブジェクトを上手く扱うことができなければ、JavaScriptの良いコードは書けません。また、JavaScriptにおけるオブジェクトの考え方は、JavaやC++とは根本的に違っています。

 そこで、この記事では、JavaScriptにおけるオブジェクトの基本的な性質について見ていくと共に、JavaやC++といった他のオブジェクト指向言語との違い、JavaScriptにおけるオブジェクトの扱い方などを解説していきたいと思います。

 なお、この記事はJavaScriptの解説ですが、その内容は、標準仕様のECMAScriptで扱われる範囲に基づいています。従って、同じくECMAScriptを元にしている言語(JScriptActionScript)でも通じる内容になっています。

対象読者

 プログラミングの基本的な知識、ならびにオブジェクトやメソッドと言った基礎的な概念については、ここでは解説しません。最低限、オブジェクト指向プログラミングについて理解をしている人を対象としています。

 この記事は、主に以下のような人を対象としています。

  • サーバサイドJavaを行っていて、JSPなどでJavaScriptの知識が必要になった人
  • JavaScriptをより深く使いこなしたい人
  • オブジェクト指向プログラミングを別のアプローチから捉えなおしてみたい人
  • JavaScriptでクラスを作る方法は知っているが、何故そのように書くとクラスを作れるのかわからない人

必要な環境

 ECMAScript 3rd editionの実装系。例えば、下記のような環境。

  • Microsoft Internet Explorer 5.5以上
  • Firefox 1.0以上

目次

  1. 「動的にメンバを定義する」ということ
  2. メンバのアクセスには二通りの方法がある
  3. for inループ
  4. プロパティの属性

「動的にメンバを定義する」ということ

 JavaやC++といった言語では、オブジェクトが持つメンバの数は、そのオブジェクトが生成された時から消滅するまで常に変わりません。Javaにおけるメンバはクラスによって定義され、オブジェクトはクラスの定義から外れることができないのです(List1.1)。

List1.1 Javaにおけるメンバの定義
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)。オブジェクトのメンバは、クラスではなくそのオブジェクト自身が定義します。つまり、各オブジェクトが自由にメンバを追加したり削除したりすることができるのです。

*1
 言語機構として存在しないだけで、概念としては存在します。

 では、そのメンバの定義はどのように行うのでしょうか。

 これは非常に簡単で、定義したいメンバに代入を行うだけです。JavaScriptの代入式は、左辺のメンバが既に定義されていたら上書きして代入を行い、まだ定義されていなければ、そのメンバを定義し初期値に右辺の値を設定します(List1.2)。

List1.2 JavaScriptにおけるメンバの定義
var obj = new Object();
alert(obj.field);  // 未定義。未定義の場合 undefined と評価される。
obj.field = 10;    // 代入により field というメンバが定義された。
alert(obj.field);  // 10 と評価される。

 つまり、オブジェクトに対し代入を行っていくことで、そのオブジェクトのメンバを追加していくことができるわけです。

 次にメンバの削除について見てみましょう。定義したメンバは、delete演算子を用いることで削除することができます(List1.3)。

List1.3 delete演算子によるメンバの削除
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)。

List1.4 括弧記法
var obj = new Object();
obj.member = "hoge";
var str = "member";
alert(obj.member == obj[str]);    // true

 この括弧記法を応用することで、ただのオブジェクトを連想配列のように扱うことができます。特にECMA262-3からは、日本語文字を含む多数のUnicode文字も識別子として使用できるようになりました(*2)。これにより、特に複雑なメソッドが必要ない限り、大概の連想配列なら通常のオブジェクトで十分に事足ります。

*2
 スクリプト自体の文字コードの扱いは実装に依存します。

for inループ

 オブジェクトを連想配列として扱った場合、その連想配列が持つ全ての要素に対してアクセスしたい、即ち、そのオブジェクトが持つ全てのメンバに対してアクセスしたい、という場合があります。JavaScriptでは、その要求を満たすためにfor inという制御構造が提供されています。

 これは、あるオブジェクトに対しそのオブジェクトが持つメンバが尽きるまでループを繰り返す、というもので、その構文はfor(ループカウンタinオブジェクト) ステートという形になります。

 オブジェクトと書かれた部分に、メンバを列挙したいオブジェクトを指定すると、ループ中では、ループカウンタ変数にそのメンバの識別子が代入されます。従って、括弧記法を用いることで、オブジェクトのメンバにアクセスすることが可能となります(List 1.5)。

List1.5 for inループ
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)を見てください。

List1.6 Stringのlength
// ソース
var str = new String("This is a string.");
alert(str.length);

// 実行結果
17

 文字列Stringのオブジェクトを作成し、そのlengthプロパティを参照している簡単なプログラムです。Stringlengthプロパティは、その文字列の文字数を表すプロパティなので、"This is a string." の場合、17と評価されます。

 続いて、次のコード(List 1.7)も見てください。

List1.7 Stringのメンバ出力プログラム
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の結果になります。

List1.8 List1.7の実行結果
prop1 = Property
prop2 = 256

 さて、ここで注目して欲しいのは、List 1.8でlengthというプロパティが出力されていないことです。List 1.6から、文字列オブジェクトがlengthというプロパティを持つことは確実です。しかし、for inでメンバを列挙してもlengthプロパティは見つかりませんでした。これはいったい何故でしょうか。

 実はこれは、文字列オブジェクトのlengthプロパティがDontEnumという属性を持っているからなのです。このDontEnum属性を持つプロパティはfor inループで列挙されなくなります。

 このように、オブジェクトのプロパティには属性というものが存在します。属性を持つプロパティは、その所有する属性に定義された性質を持つことになるのです。

 属性には、上記のfor inで列挙されないDontEnumの他に、代入処理が無視されるReadOnlydelete演算子での削除処理が無視されるDontDelete、直にアクセスできないInternalがあります。

 これらプロパティの属性は、基本的にはプログラマがコントロールすることができません(*3)。つまり、プログラマが定義したプロパティにDontDeleteを与えたり、文字列オブジェクトのlengthからDontEnumを外したり、などということはできません。

*3
 プログラマの操作によって、属性付きのプロパティが作成される事はありますが、あくまでシステムが定義する振る舞いの結果そうなるというだけです。

 従って、これらの属性を持つプロパティというのは、基本的に組込みのオブジェクトが持つと言うことになります。

まとめ

 今回は、以下のようなオブジェクトの基本的な性質とその操作の方法を学びました。

  • オブジェクトのメンバは、プログラム中で動的に追加・削除できる。
  • メンバのアクセスには、ドット記法と括弧記法の2通りがある。
  • for inというメンバを列挙する制御構造がある。
  • プロパティは、属性とよばれる性質を持つことがある。

 次回は、JavaScript関数の基本について解説します。

参考資料

 本稿は、Starry Night 『JavaScript講座』にて公開したものです。連載を再開するにあたり、再編集を行いCodezineに寄稿いたしました。



  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 中村 学(ナカムラ マナブ)

    イー・デスク株式会社 開発部 部長。業務ではJava/PHPによるWebアプリケーション開発を行っている。学生時代はユーザインターフェイスデザインを専攻し、アクセシビリティ、ユーザビリティの観点からスタイルシート、JavaScript等の技術に造詣を深める。最近はRubyとLispに注目している。...

バックナンバー

連載:JavaScriptによるオブジェクト指向プログラミング
All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5