ECMAScript/オブジェクト

Last-modified: 2012-11-25 (日) 22:06:13

Object

Objectは、プロパティの順序なし集合。

プロパティは、以下の組である。

  1. 名前
  2. アトリビュートの集合

※オブジェクト初期化子の文法(ECMAScript3rd:11.1.5)から判断して、名前には任意の文字列(空文字列含む)が使用できるものと思われる。

var baz=new Object();
baz[""]="hoge";
baz[5]="hogehoge";
//baz.6="hogehogehoge";  //文法エラー
for (var j in baz) {
  document.write("baz."+j+"="+baz[j]+"\n");
}
baz.=hoge
baz.5=hogehoge

アトリビュート

アトリビュートは、プロパティに対する操作の可否を表す。
アトリビュートは、プログラムから操作できるわけではない。ECMAScriptの挙動の説明のために用いられる。

アトリビュート意味
ReadOnly読み取り専用。書き込み不可。ただし、プロパティの値が不変というわけではなく、ホスト環境によって実行された処理により変化する場合もある。
DontEnumfor-in構文使用時に無視されるプロパティ
DontDeletedelete演算子で削除できないプロパティ
Internal内部プロパティであり、プログラムから直接アクセスできない

内部プロパティ

内部プロパティは、ECMAScriptの仕様書では、[[プロパティ名]]と表記される。
一覧はECMAScript3rd:8.6.2を参照。

主なもの。

プロパティ意味
[[Prototype]]このオブジェクトのプロトタイプ
[[Class]]オブジェクトの種類を表す文字列。Object.prototype.toStringで参照される。
[[Scope]]関数オブジェクトに存在し、その関数のスコープチェインを表す。

※このほか、関数呼び出し時、コンストラクタ呼び出し時、プロパティの値の読み取り/書き換え時などの処理を行う関数を内部プロパティとして定義している。ECMAScriptの定義では、関数呼び出し(foo(a,b,...))やプロパティへのアクセス(foo.a)などのセマンティクス(動作)を直接定義せずに、内部プロパティ(関数)の呼び出しとして定義している。つまり、実際の挙動は内部プロパティとして与えられた関数の内容に依存する。このように遠回りな仕様定義をしているのは、実行環境が提供するオブジェクトの挙動(関数呼び出し、プロパティアクセスなど)はECMAScript仕様では定義せず、実行環境が任意に定義できるようにするためであると考えられる。

ネイティブオブジェクト、ビルトインオブジェクト、ホストオブジェクト

ネイティブオブジェクト
ホスト環境(実行環境)には依存しないオブジェクト
ビルトインオブジェクト
ネイティブオブジェクトで、ECMAScriptプログラム実行時に存在するもの。
ホストオブジェクト
ホスト環境(実行環境)によって提供されるオブジェクト

利用者定義のオブジェクトはネイティブオブジェクトである。

継承

ECMAScriptは"プロトタイプに基づく継承"を採用している。JavaやC++のような"クラス"は存在しない。

プロトタイプ

すべてのオブジェクトは、そのオブジェクトの"プロトタイプ"への暗黙的な参照を持っている。プロトタイプもオブジェクトである。したがって、プロトタイプのプロトタイプも存在する。プロトタイプをたどっていくと、最終的にプロトタイプがnullであるようなオブジェクトに到達する。プロトタイプが無限に連鎖することはない。

※ Javaの場合、全クラスに共通(唯一)のトップレベルのスーパークラスとしてObjectがあるが、ECMAScriptの場合は、唯一のトップレベルのプロトタイプがあるとは明記されていない。new Object() で作成されたオブジェクトのプロトタイプは、トップレベルである(そのプロトタイプがnull)が、それが全オブジェクトで共有されるかどうかについては、明記がない。

同じコンストラクタを使って作成されたオブジェクトは、同一のプロトタイプを共有する。

プロパティの検索

オブジェクトoのプロパティpを読み取る場合、(ECMAScript3rd:8.6.2.1)

  1. oにpが存在すれば、それを参照する
  2. 存在しない場合、oのプロトタイプにpが存在すれば、それを参照する
  3. プロトタイプにも存在しない場合、同様にさらにそのプロトタイプを検索する。たどるべきプロトタイプがなくなってしまった場合(プロトタイプがnullの場合)、undefined。

オブジェクトoのプロパティpに値を代入する場合、(ECMAScript3rd:8.6.2.2)

  1. oにpが存在すれば、それを書き換える(代入する)
  2. 存在しない場合、oにプロパティpを作成し、それに値を代入する

function Oya(){
  this.foo1="foo1Value";
  this.foo2="foo2Value";
}

function Ko(){
  this.bar="barValue";
}
Ko.prototype=new Oya();


var ko1=new Ko();
var ko2=new Ko();

ko1.bar="ko1barValue";
document.write("ko1.bar="+ko1.bar+"\n");   //→ko1barValue
document.write("ko2.bar="+ko2.bar+"\n");   //→barValue

Ko.prototype.foo1="fff1Value";
document.write("ko1.foo1="+ko1.foo1+"\n"); //→fff1Value
document.write("ko2.foo1="+ko2.foo1+"\n"); //→fff1Value

ko1.foo1="ko1foo1Value";
document.write("ko1.foo1="+ko1.foo1+"\n"); //→ko1foo1Value
document.write("ko2.foo1="+ko2.foo1+"\n"); //→fff1Value

13行目まで実行後
inherit1.PNG

最後まで実行後
inherit2.PNG
→ko1.foo1とすると、ko1のfoo1が見える。ko2.foo1とするとKo.prototype.foo1が見える

コンストラクタ

コンストラクタは、オブジェクトを作成・初期化するFunctionオブジェクト(関数)である。

fをFunctionオブジェクトとすると、

new f(...)

により新しいオブジェクトoが作成され、f.prototypeがoのプロトタイプとなる。



厳密には、Functionオブジェクトのうち、内部プロパティ[[Construct]]を持つものをコンストラクタと呼ぶ。[[Construct]]のないFunction fに対してnew fを実行するとTypeErrorとなる。

利用者定義関数にはすべて[[Construct]]が与えられるため、利用者定義関数はすべてコンストラクタとなる。

組み込み関数には、コンストラクタでないものもある。例:

  • Array → コンストラクタ
  • Array.prototype.toString → コンストラクタではない。

コンストラクタをnewを使わずに呼び出した場合

コンストラクタは関数なので、newを使わずに関数として呼び出すことが可能。

  • 利用者定義関数をnewなしで呼び出した場合、通常の関数呼び出しと同じ扱いとなる。オブジェクトは新規作成されない。thisは通常の関数呼び出しと同じ規則によって決まる。
  • String, Numberなど組み込み関数をnewなしで呼び出した場合の挙動は、その関数に依存し、ECMAScriptの仕様によって定義されている(ECMAScript3rd:4.2.1)。例:
    • Object() は new Object()と同じ動作となる。
    • String(v), Number(v)は、vをString型、Number型(どちらも、オブジェクトではなく型のほう)に変換した結果を返す(new付きの場合と異なる挙動)。

コンストラクタでreturnを使った場合

new f を実行し、fがreturn文でオブジェクトを返却した場合、new fの結果はその返却されたオブジェクトとなる。newによって新規に作成されたオブジェクトではない(ECMAScript3rd:13.2.2)。

 function SomeObject() {
   this.someObjectProp="yyy";
   return new String("aaa");
 }
 var sss=new SomeObject();  // →sssはStringオブジェクト

Functionオブジェクトのprototypeプロパティ

以下の記述が評価されるタイミングでFunctionオブジェクトが作成される。

function name(param){body...}
function (param){body...}

このとき、作成されたFunctionオブジェクトのprototypeプロパティには、新規に作成された空のオブジェクト( new Object() )がセットされる。(ECMAScript3rd:13.2)

なお、同じfunctionの記述が何度も評価される場合、以前作成したFunctionオブジェクトの再利用をすることを許容している(ECMAScript3rd:13.2の最後のNote参照)。この場合、prototypeは以前のFunctionオブジェクトからコピーされ(したがって共有され)る。prototypeのプロパティを書き換えるような使い方をしている場合には、このような再利用がなされると影響をうけることになるはず。なお、ここでいう「再利用」は厳密にはjoined objectと呼んで、ECMAScript3rd:13.1.2で定義されている。

instanceof演算子

A instanceof B

Bはコンストラクタ*1

  • AのプロトタイプとB.prototypeが同一のオブジェクトなら*2→true
  • AのプロトタイプのプロトタイプとB.prototypeとを同様に比較。
  • 以下同様。プロトタイプがnullになったら→false。

Aがオブジェクトでないならfalse

function Oya(){
  this.foo1="foo1Value";
  this.foo2="foo2Value";
}

function Ko(){
  this.bar="barValue";
}
Ko.prototype=new Oya();

var ko1=new Ko();
var oya1=new Oya();

document.write("ko1 instanceof Oya="+ (ko1 instanceof Oya) +"\n");    //→true
document.write("oya1 instanceof Oya="+ (oya1 instanceof Oya) +"\n");  //→true
document.write("oya1 instanceof Ko="+ (oya1 instanceof Ko) +"\n");    //→false
document.write("1 instanceof Ko="+ (1 instanceof Ko) +"\n");          //→false

isPrototypeOf関数

isPrototypeOfは、Object.prototypeが持っている関数。

A.isPrototypeOf(B)
  • BのプロトタイプがAである→true
  • BのプロトタイプのプロトタイプがAである→true
  • 以下同様。プロトタイプがnullになったら→false

Bがオブジェクトでなければfalse。

function Oya(){
  this.foo1="foo1Value";
  this.foo2="foo2Value";
}

function Ko(){
  this.bar="barValue";
}
Ko.prototype=new Oya();

var ko1=new Ko();

document.write("Ko.isPrototypeOf(ko1)=" +(Ko.isPrototypeOf(ko1))+"\n");                     //→false
document.write("Ko.prototype.isPrototypeOf(ko1)=" +(Ko.prototype.isPrototypeOf(ko1))+"\n"); //→true
document.write("new Oya().isPrototypeOf(ko1)=" +(new Oya().isPrototypeOf(ko1))+"\n");       //→false

クラスに基づく継承との比較

ECMAScriptクラスに基づく継承(Javaなど)
メソッドはオブジェクトによって与えられるメソッドはクラスによって与えられる
メソッドと状態(プロパティの値)が継承されるメソッドは継承される。状態は継承されない。
動的にプロパティを追加できる。オブジェクトにプロパティを追加すれば、そのオブジェクトをプロトタイプとするすべてのオブジェクトから参照可能となる。動的にプロパティを追加できない。

プロパティの操作

プロパティへのアクセス

 A.foo
 A["foo"]

上記はどちらもオブジェクトAのプロパティfooを表す。

delete演算子

delete A.foo
delete A["foo"]

オブジェクトAのプロパティfooを削除する。

  • もともとプロパティfooがない場合はtrueを返す。
  • 削除が成功した場合、削除してtrueを返す。
  • プロパティがアトリビュートDontDeleteをもっている場合は削除できない。falseを返す。
function Oya(){
  this.foo1="foo1Value";
  this.foo2="foo2Value";
}

function Ko(){
  this.bar="barValue";
}
Ko.prototype=new Oya();

var ko1=new Ko();

document.write("delete ko1.bar="+(delete ko1.bar)+"\n");   //→true
document.write("ko1.bar="+ko1.bar+"\n");                   //→undefined
document.write("delete ko1.foo1="+(delete ko1.foo1)+"\n"); //→true
document.write("ko1.foo1="+ko1.foo1+"\n");                 //→foo1Value

ko1.foo2="modified foo2Value";
document.write("ko1.foo2="+ko1.foo2+"\n");                 //→modified foo2Value
document.write("delete ko1.foo2="+(delete ko1.foo2)+"\n"); //→true
document.write("ko1.foo2="+ko1.foo2+"\n");                 //→foo2Value

document.write("delete ko1="+(delete ko1)+"\n");           //→false
      // このプログラムの場合ko1はグローバルオブジェクトのプロパティとなるが、
      // varで宣言したものはDontDeleteアトリビュートがつくため、削除できない。

hasOwnProperty関数

hasOwnPropertyは、Object.prototypeが持っている関数。

 A.hasOwnProperty(foo)

fooは文字列。

  • Aがプロパティfooを持っている→true
  • そうでなければ→false

この関数は、Aのプロトタイプを探索しない

function Oya(){
  this.foo1="foo1Value";
  this.foo2="foo2Value";
}

function Ko(){
  this.bar="barValue";
}
Ko.prototype=new Oya();

var ko1=new Ko();

document.write("ko1.hasOwnPrototype(\"bar\")=" + ko1.hasOwnProperty("bar")+"\n");   //→true
document.write("ko1.hasOwnPrototype(\"foo1\")=" + ko1.hasOwnProperty("foo1")+"\n"); //→false

ko1.foo1="ko1foo1Value";
document.write("ko1.hasOwnPrototype(\"foo1\")=" + ko1.hasOwnProperty("foo1")+"\n"); //→true

*1 厳密には、prototypeプロパティと[[HasInstance]]メソッドを持つオブジェクト
*2 厳密には、同一か、互いにjoinedの関係のオブジェクトなら