JavaScript は、簡潔なオブジェクトベースのパラダイムをもつ言語として設計されました。 JavaScript におけるオブジェクトは、プロパティ (名前と値の組) の集まりです。 プロパティの値として関数を用いることもでき、プロパティの値として使われる関数はそのオブジェクトのメソッドとなります。 JavaScript 実行環境に予め定義されているオブジェクトを使うだけでなく、プログラマ自身がオブジェクトを定義することもできます。
この章では、オブジェクトやプロパティ、関数やメソッドの使い方と、プログラマ自身がオブジェクトを定義する方法を説明します。
オブジェクトの概観
他の多くのプログラミング言語でもそうであるように、JavaScript におけるオブジェクトも、現実世界の 「もの」 (すなわちオブジェクト) になぞらえることができます。 JavaScript におけるオブジェクトの概念は、現実世界に実在する 「もの」 との対比で解釈できます。
JavaScript において、オブジェクトはプロパティを伴った独立した存在です。 カップを例に考えてみましょう。 カップは様々な特性 (プロパティ) をもったもの (オブジェクト) です。 カップには、色や形状、重さや材質といった性質があります。 同様に、JavaScript のオブジェクトもプロパティをもつことができ、プロパティによってそのオブジェクトの特徴を表すことができます。
オブジェクトとそのプロパティ
JavaScript のオブジェクトは、自身に関連付けられたプロパティを持ちます。オブジェクトのプロパティは、オブジェクトに関連付けられている変数と捉える事ができます。オブジェクトのプロパティは基本的に、オブジェクトに属するものという点を除いて通常の JavaScript 変数と同じようなものです。オブジェクトのプロパティは、オブジェクトの特性を定義します。以下の様に、オブジェクト名とそのプロパティを単純にドット演算子で繋いで記述する事でオブジェクトのプロパティへアクセス可能です。
全ての JavaScript の変数と同じく、オブジェクト名(通常の変数にもなります)とプロパティ名では、大文字/小文字が厳密に区別されます。プロパティに値を代入することでプロパティを定義する事が出来ます。以下のようにして、myCar
という名前のオブジェクトを作成し、それに対し make
、model
、year
という名前のプロパティを与えることができます
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_i
に代入されます。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文字目を大文字にします。
new
を使ってオブジェクトのインスタンスを作ります。
オブジェクトの型を定義するために、名前、プロパティ、メソッドを定義するような関数を作ります。例えば、車のオブジェクト型を作りたいとしましょう。この型の名前は car
で、プロパティ make
、model
、year
を持たせたいと思いました。このためには、次の関数を書くことになるでしょう:
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
のための引数として渡していることに注意してください。これで、car2
のowner
の名前を知りたければ、次のプロパティにアクセスできます:
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型のオブジェクトに追加し、オブジェクト car1
の color
プロパティに値を代入します。
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();
この結果、次のような出力が作られます。
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
をご覧ください。
参照
- ECMAScript 5.1 spec: Language Overview
- JavaScript. The core. (Dmitry A. Soshnikov ECMA-262 article series)