Working with Objects

JavaScript は、簡潔なオブジェクトベースのパラダイムをもつ言語として設計されました。 JavaScript におけるオブジェクトは、プロパティ (名前と値の組) の集まりです。 プロパティの値として関数を用いることもでき、プロパティの値として使われる関数はそのオブジェクトのメソッドとなります。 JavaScript 実行環境に予め定義されているオブジェクトを使うだけでなく、プログラマ自身がオブジェクトを定義することもできます。

この章では、オブジェクトやプロパティ、関数やメソッドの使い方と、プログラマ自身がオブジェクトを定義する方法を説明します。

オブジェクトの概観

他の多くのプログラミング言語でもそうであるように、JavaScript におけるオブジェクトも、現実世界の 「もの」 (すなわちオブジェクト) になぞらえることができます。 JavaScript におけるオブジェクトの概念は、現実世界に実在する 「もの」 との対比で解釈できます。

JavaScript において、オブジェクトはプロパティを伴った独立した存在です。 カップを例に考えてみましょう。 カップは様々な特性 (プロパティ) をもったもの (オブジェクト) です。 カップには、色や形状、重さや材質といった性質があります。 同様に、JavaScript のオブジェクトもプロパティをもつことができ、プロパティによってそのオブジェクトの特徴を表すことができます。

オブジェクトとそのプロパティ

JavaScript のオブジェクトは、自身に関連付けられたプロパティを持ちます。オブジェクトのプロパティは、オブジェクトに関連付けられている変数と捉える事ができます。オブジェクトのプロパティは基本的に、オブジェクトに属するものという点を除いて通常の JavaScript 変数と同じようなものです。オブジェクトのプロパティは、オブジェクトの特性を定義します。以下の様に、オブジェクト名とそのプロパティを単純にドット演算子で繋いで記述する事でオブジェクトのプロパティへアクセス可能です。

objectName.propertyName

全ての JavaScript の変数と同じく、オブジェクト名(通常の変数にもなります)とプロパティ名では、大文字/小文字が厳密に区別されます。プロパティに値を代入することでプロパティを定義する事が出来ます。以下のようにして、myCar という名前のオブジェクトを作成し、それに対し makemodelyear という名前のプロパティを与えることができます

var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;

JavaScript オブジェクトのプロパティは、ブラケット表記法でもアクセスや設定ができます。オブジェクトは連想配列と呼ばれることがあります。個々のプロパティが、アクセスのために使われる文字列値と関連づけられているからです。ですから例えば、 myCar オブジェクトのプロパティに次のようにアクセスできます:

myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;

オブジェクトプロパティの名前には、正しい JavaScript 文字列か、空文字列を含む、文字列に変換できるあらゆるものを使えます。しかしながら、JavaScript 識別子として正しくないプロパティ名(例えば空白やダッシュを含んでいたり、数字で始まったりするプロパティ名)には、ブラケット(角括弧)表記法でのみアクセスできます。この表記法はプロパティ名を動的に決める場合(プロパティ名が実行時に決まる場合)に便利です。例を示します:

var myObj = new Object(),
    str = "myString",
    rand = Math.random(),
    obj = new Object();

myObj.type              = "Dot syntax";
myObj["date created"]   = "String with space";
myObj[str]              = "String value";
myObj[rand]             = "Random Number";
myObj[obj]              = "Object";
myObj[""]               = "Even an empty string";

console.log(myObj);

プロパティには、変数内の文字列値を使ったアクセスもできます:

var propertyName = "make";
myCar[propertyName] = "Ford";

propertyName = "model";
myCar[propertyName] = "Mustang";

ブラケット表記法を for...in で使い、オブジェクトの列挙可能なプロパティすべてを巡回できます。動作説明用の次の関数は、オブジェクトとオブジェクト名を引数として取り、すべてのプロパティを表示します:

function showProps(obj, objName) {
  var result = "";
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
        result += objName + "." + i + " = " + obj[i] + "\n";
    }
  }
  return result;
}

そして、関数を showProps(myCar, "myCar") のように呼び出すと次の結果が返ります:

myCar.make = Ford
myCar.model = Mustang
myCar.year = 1969

すべてはオブジェクト

JavaScript では、ほぼあらゆるものがオブジェクトです。null と undefined 以外のすべてのプリミティブ型はオブジェクトとして扱われます。プロパティを代入でき(代入されたプロパティには非永続的なものもあります)、オブジェクトのすべての特徴を持ちます。

オブジェクトの全プロパティの列挙

ECMAScript 5 からは、オブジェクトプロパティをリスト/トラバース(横断)するための言語固有な方法が3つあります:

  • for...in ループ
    このメソッドは、オブジェクトとオブジェクトのプロトタイプチェインにある列挙可能なプロパティをすべてトラバースします
  • Object.keys(o)
    このメソッドは、そのオブジェクト独自の(プロトタイプチェインを除く)、すべての列挙可能なプロパティ名("keys")を、配列で返します
  • Object.getOwnPropertyNames(o)
    このメソッドは、そのオブジェクト独自のすべてのプロパティ名(列挙可能かどうかに関わらず)を配列で返します

ECMAScript 5 には、オブジェクトの全プロパティをリストする言語固有の方法はありません。しかしながら、次の関数で実現できます:

function listAllProperties(o){     
	var objectToInspect;     
	var result = [];
	
	for(objectToInspect = o; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect)){  
		result = result.concat(Object.getOwnPropertyNames(objectToInspect));  
	}
	
	return result; 
}   

これは"隠された"プロパティ(プロパティチェイン上に先に現れる、同名の別のプロパティ)を見つけるのに便利かもしれません。配列内の重複を取り除かないと、アクセス可能なプロパティをリストするのは容易ではありません。

新しいオブジェクトの作成

JavaScript には多くの定義済みオブジェクトがあります。さらに、独自のオブジェクトを定義できます。JavaScript 1.2 以降では、オブジェクト初期化子を使ってオブジェクトを作れます。もしくは、初めにコンストラクター関数を作り、その関数と new 演算子を使ってオブジェクトをインスタンス化します。

オブジェクト初期化子の利用

コンストラクタ関数を使ったオブジェクトの作成に加えて、オブジェクト初期化子を使ってオブジェクトを作ることができます。オブジェクト初期化子を使うことを、オブジェクトをリテラル表記法で作ると表現することがあります。"オブジェクト初期化子"は、C++で使われる用語と一致します。

オブジェクト初期化子を使ったオブジェクト構文は次のようになります:

var obj = { property_1:   value_1,   // property_# may be an identifier...
            2:            value_2,   // or a number...
            // ...,
            "property n": value_n }; // or a string

obj は新しいオブジェクトの名前、property_i は識別子(名前、数値、または文字列リテラルのいずれか)、value_i は式で、その値が property_に代入されます。obj と代入はオプションです; もし他の場所でこのオブジェクトを参照する必要がないのなら、変数への代入は不要です(もし文の期待される場所にオブジェクトを書くなら、リテラルとブロックステートメントを混乱しないように、オブジェクトリテラルを括弧で囲む必要があるかもしれません)。

オブジェクトがオブジェクト初期化子を使ってスクリプトのトップレベルで作られると、JavaScript はそのオブジェクトリテラルを含む式を評価するたびにオブジェクトを作ります。さらに、関数内の初期化子は関数が呼ばれるたびに作られます。

次の文は式 cond が trueの場合にのみオブジェクトを作り、変数xに代入します。

if (cond) var x = {hi: "there"};

次の例では3つのプロパティを持った myHonda を作ります。engine プロパティもまた、それ自身のプロパティを持つオブジェクトであることに注意してください。

var myHonda = {color: "red", wheels: 4, engine: {cylinders: 4, size: 2.2}};

配列を作るのにオブジェクト初期化子を使うこともできます。array literalsをご覧ください。

JavaScript 1.1 以前では、オブジェクト初期化子は使えません。コンストラクター関数か、他のオブジェクトがそのために用意している関数を使ってオブジェクトを作ります。Using a constructor functionをご覧ください。

コンストラクター関数の利用

他の方法として、次の二つの手順によりオブジェクトを作ることができます:

  1. コンストラクター関数を記述してオブジェクトの型を定義します。正当な理由のある習慣ですが、1文字目を大文字にします。
  2. new を使ってオブジェクトのインスタンスを作ります。

オブジェクトの型を定義するために、名前、プロパティ、メソッドを定義するような関数を作ります。例えば、車のオブジェクト型を作りたいとしましょう。この型の名前は car で、プロパティ makemodelyear を持たせたいと思いました。このためには、次の関数を書くことになるでしょう:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

this を使うことで、関数に渡されたオブジェクトのプロパティに対して値を代入することに注意してください。

これで、mycar という名前のオブジェクトを次のようにして作ることができます:

var mycar = new Car("Eagle", "Talon TSi", 1993);

この文は mycar を作り、プロパティに指定した値を代入します。mycar.make の値は文字列 "Eagle"になり、mycar.year は整数 1993になり、といった具合です。

new を使って car オブジェクトをいくつでも作れます。例えば:

var kenscar = new Car("Nissan", "300ZX", 1992);
var vpgscar = new Car("Mazda", "Miata", 1990);

オブジェクトは、それ自身が他のオブジェクトであるプロパティを持つことができます。例えば、person という名前のオブジェクトを次のように定義するものとします:

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

それから、2つの新しい person オブジェクトを次のようにインスタンス化します:

var rand = new Person("Rand McKinnon", 33, "M");
var ken = new Person("Ken Jones", 39, "M");

それから、person オブジェクトの入る owner プロパティを追加して、car の定義を次のように書き直します:

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
}

新しいオブジェクトのインスタンスを得るために、次のようにします:

var car1 = new Car("Eagle", "Talon TSi", 1993, rand);
var car2 = new Car("Nissan", "300ZX", 1992, ken);

新しいオブジェクトを作る際、リテラル文字列や整数値を渡す代わりに、上の文ではオブジェクト rand と ken をownerのための引数として渡していることに注意してください。これで、car2ownerの名前を知りたければ、次のプロパティにアクセスできます:

car2.owner.name

すでに定義されたオブジェクトにはいつでもプロパティを追加できることに注意してください。例えば次の文は

car1.color = "black";

プロパティ color を car1 に追加し、"black" という値をそこに代入します。しかしながら、これは他のどのオブジェクトにも影響しません。同じ型のすべてのオブジェクトに新しいプロパティを追加するには、car オブジェクト型の定義にそのプロパティを追加する必要があります.

Object.create メソッドの利用

オブジェクトは Object.create メソッドを使っても作れます。コンストラクター関数の定義なしに、作りたいオブジェクトのプロトタイプを選べるため、このメソッドは大変便利です。メソッドの詳細情報や使い方については、Object.create methodをご覧ください。

継承

JavaScript のすべてのオブジェクトは、少なくとも1つの他のオブジェクトを継承しています。継承元になるオブジェクトは prototype として知られ、継承されたプロパティはコンストラクターの prototype  オブジェクトにあります。

オブジェクトプロパティのインデックスづけ

JavaScript 1.0 では、オブジェクトのプロパティを、プロパティ名またはインデックスを使って参照できます。しかしながら JavaScript 1.1 以降では、プロパティを初めに名前で定義した場合には常に名前で参照する必要があり、インデックスで定義した場合には常にインデックスで参照する必要があります。

この制限は、オブジェクトとプロパティをコンストラクタ関数を使って作るとき(上で Car オブジェクト型に対してしたように)や、個々のプロパティを明示的に定義した場合(例えば myCar.color = "red")に適用されます。初めにオブジェクトプロパティをインデックスで定義した場合、たとえば myCar[5] = "25 mpg"のようにした場合には、その後はそのプロパティを myCar[5]のようにしないと参照できません。

このルールの例外になるのは 、例えば forms 配列のような HTML から反映されたオブジェクトです。いつでも、この配列にあるオブジェクトはインデックス(文書中に現れる位置基準)と名前(名前が定義されていれば)のどちらからも参照できます。例えば、文書内の2つ目の <FORM> タグが NAME 属性として"myForm" という値を持つとき、フォームは document.forms[1]、document.forms["myForm"] 、document.myForm のいずれかで参照できます。

object 型のプロパティ定義

定義済みのオブジェクト型に対して、 prototype プロパティを使ってプロパティを追加できます。このプロパティは1つのインスタンスオブジェクトだけではなく、指定型のすべてのオブジェクトで共有されます。次のコードは color プロパティをすべてのcar型のオブジェクトに追加し、オブジェクト car1color プロパティに値を代入します。

Car.prototype.color = null;
car1.color = "black";

さらなる情報は JavaScript Reference 内にある、Functionオブジェクトの prototypeプロパティをご覧ください。

メソッドの定義

メソッドはオブジェクトに関連付けられた関数です。簡単に言えば、オブジェクトのプロパティのうち関数であるものがメソッドです。メソッドは通常の関数と同じ方法で定義されますが、オブジェクトのプロパティに代入される点が異なります。例えば:

objectName.methodname = function_name;

var myObj = {
  myMethod: function(params) {
    // ...do something
  }
};

objectName は既存のオブジェクトを、methodname はメソッド名にしたい名前を、function_name は関数の名前を指しています。

そしてオブジェクトのメソッドは次のようにして呼べます:

object.methodname(params);

特定のオブジェクト型のためのメソッドは、そのオブジェクトのコンストラクター関数にメソッド定義を含めることで定義できます。例えば、定義済みの car オブジェクトのために、プロパティを整形して表示する関数を定義できます。例えば、

function displayCar() {
  var result = "A Beautiful " + this.year + " " + this.make
    + " " + this.model;
  pretty_print(result);
}

pretty_print は水平な定規と文字列を表示する関数です。this を使って、メソッドが属するオブジェクトを参照していることに注意してください。

次の文をオブジェクトの定義に追加すると、この関数を car のメソッドにできます。

this.displayCar = displayCar;

最終的には、car の完全な定義は次のようになるでしょう

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
  this.displayCar = displayCar;
}

次のようにして、個々のオブジェクトに対してdisplayCar メソッドを呼び出せます:

car1.displayCar();
car2.displayCar();

この結果、次のような出力が作られます。

Image:obja.gif

Figure 7.1: Displaying method output.

this をオブジェクトの参照に使う

JavaScript は特別なキーワード this は、メソッド内でカレントオブジェクトを参照するのに使います。例えば、 validate という名前の、オブジェクトの value  プロパティを検証する関数があり、引数としてオブジェクト、最小値、最大値を渡すものとします:

function validate(obj, lowval, hival) {
  if ((obj.value < lowval) || (obj.value > hival))
    alert("Invalid Value!");
}

これで、各form要素のonchangeイベントハンドラから、validate を次の例のように呼び出せます:

<input type="text" name="age" size="3"
  onChange="validate(this, 18, 99)">

通常は、メソッド内で this は呼び出し元オブジェクト(calling object)を参照しています。

form プロパティと組み合わせると、this は現在のオブジェクトの親フォームを参照できます。次の例では、フォーム myForm  は Text オブジェクトとボタンを持っています。ユーザーがボタンをクリックすると、Text オブジェクトの値がフォームの名前に設定されます。ボタンの onclick イベントハンドラは親フォームである myForm を参照するのに this.form を使います。

<form name="myForm">
<p><label>Form name:<input type="text" name="text1" value="Beluga"></label>
<p><input name="button1" type="button" value="Show Form Name"
     onclick="this.form.text1.value = this.form.name">
</p>
</form>

getter と setter の定義

getter は特定のプロパティ値を取得するためのメソッドです。setter は特定のプロパティ値を設定するためのメソッドです。 定義済みのコアオブジェクトのいずれか、または新たなプロパティの追加ができるユーザー定義オブジェクトに、getter と setter を定義できます。getter と setter の定義に使う構文はオブジェクトリテラル構文です。

JavaScript 1.8.1 における注記

JavaScript 1.8.1から、オブジェクトや配列の初期化子でプロパティを設定したときには setter は呼ばれなくなりました。

次の JS shell セッションでは、ユーザー定義オブジェクト o に対して getter や setter がどのように動作しているか知ることができます。JS shell は、開発者がバッチモードまたは対話的モードで、JavaScriptコードをテストできるアプリケーションです。

js> var o = {a: 7, get b() {return this.a + 1;}, set c(x) {this.a = x / 2}};
[object Object]
js> o.a;
7
js> o.b;
8
js> o.c = 50;
js> o.a;
25

o オブジェクトのプロパティは:

  • o.a — 数値
  • o.b — getterで、o.a 足す 1 を返します
  • o.c — setterで、o.a の値を o.c に設定されようとする値の半分に設定します

[gs]et propertyName(){ } という構文が誤解させるかもしれませんが、オブジェクトリテラル内で "[gs]et property()" (この下の__define[GS]etter__ とは対照的です)のように定義された getter や setter の関数名は getterそれ自身の名前ではないことに気をつけてください。"[gs]et property()" 構文で getter や setter 内の関数に名前をつけるには、Object.defineProperty(または Object.prototype.__defineGetter__ legacy fallback)を使って、明示的に名前のついた関数を定義します。

次の JavaScript shell セッションでは、 getter と setter が Date のプロトタイプを拡張し、すべての定義済みDate クラスのインスタンスに year プロパティを追加する方法が見られます。year プロパティの getter と setter のサポートのために、Date クラスの既存の getFullYear と setFullYear メソッドを使います。

次の文は year プロパティのgetter と setterを定義します。

js> var d = Date.prototype;
js> d.__defineGetter__("year", function() { return this.getFullYear(); });
js> d.__defineSetter__("year", function(y) { this.setFullYear(y); });

次の文では Date オブジェクトの中で getter と setter を使っています。

js> var now = new Date;
js> print(now.year);
2000
js> now.year = 2001;
987617605170
js> print(now);
Wed Apr 18 11:13:25 GMT-0700 (Pacific Daylight Time) 2001

廃止された構文

過去に、JavaScript は getter と setter の定義に異なった別の構文をサポートしていました。これらの構文はどれも他のエンジンでサポートされず、過去の JavaScript で取り除かれました。取り除かれた構文や代替方法について詳しくは、this dissection of the removed syntaxes をご覧ください。

まとめ

原則として、getter と setter は次のどちらの方法でも作れます。

  • オブジェクト初期化子 による定義
  • getter や setter を追加するメソッドによる、任意のオブジェクトへの後からの追加

getter と setter を オブジェクト初期化子 を使った定義は、getter メソッドに get プレフィックスを、setter メソッドに set プレフィックスをつけるだけです。当然ですが getter メソッドは引数を想定してはいけませんし、setter メソッドはただ1つの引数(新たな設定値)を想定します。例:

var o = {
  a: 7,
  get b() { return this.a + 1; },
  set c(x) { this.a = x / 2; }
};

getter と setter はまた、2つの特別なメソッド  __defineGetter__ and __defineSetter__ を使って、生成済みのオブジェクトにいつでも追加できます。どちらのメソッドも第1引数として文字列形式で getter または setter の名前を期待します。第2引数は getter または setter として呼ばれる関数です。例を示します(前の例の続きになります):

o.__defineGetter__("b", function() { return this.a + 1; });
o.__defineSetter__("c", function(x) { this.a = x / 2; });

2つの方法のどちらを選ぶのかは、プログラミング様式やいますべき作業によります。すでにオブジェクト初期化子を気に入っているのなら、プロトタイプの定義時にほとんどの場合、最初の書式を選ぶでしょう。この書式はより簡潔で自然です。しかしながら、getter や setter を後から追加しなければならないとき — 自分がプロトタイプやオブジェクトの詳細を書いていない場合 — には、2番めの書式だけが使えます。2つ目の書式には JavaScript の動的な性質が表現されています — ただ、コードを読みにくく理解しづらいものにする可能性があります。

Firefox 3.0 以前では、getter と setter は DOM 要素には使えませんでした。より古いバージョンの Firefox では エラーなしに失敗します。例外処理が必要なら、応急措置としてHTMLElement (HTMLElement.prototype.__define[SG]etter__) のプロトタイプを変更し例外を投げるように変えることができます。

Firefox 3.0 では、定義済みのプロパティを上書きするような getter や setter の定義は例外を投げます。そうしたプロパティは前もって削除しておかねばなりません。これは古いバージョンの Firefox には当てはまりません。

参照

プロパティの削除

delete 演算子を使ってプロパティを削除できます。次のコードはプロパティの削除方法を示します:

//Creates a new object, myobj, with two properties, a and b.
var myobj = new Object;
myobj.a = 5;
myobj.b = 12;

//Removes the a property, leaving myobj with only the b property.
delete myobj.a;

delete 演算子はまた、varキーワードを使わずに定義されたグローバル変数の削除にも使えます:

g = 17;
delete g;

さらなる情報はdelete をご覧ください。

参照

添付ファイル

ファイル サイズ 日時 添付者:
obja.gif
3284 バイト 2005-04-22 08:49:02 JdeValk
understanding-underlines-figure06.gif
2038 バイト 2005-05-02 06:15:07 CitizenK

Document Tags and Contributors

Contributors to this page: sosleepy, nobuoka, ethertank, Electrolysis
最終更新者: ethertank,