2009-06-12 JavaScriptのオブジェクトについて考察してみた
JavaScriptのオブジェクトについて考察してみた
JavaScriptを勉強しているとオブジェクトとはなんぞや?ということがわからなくなってきます。選択肢が増えれば増えるほど。
JavaScriptには、同じように見えて、実は同じではないデータがあります。それらのオブジェクトについて、区別して説明が付けられるように、自分なりに考察してみました。勉強中のアウトプットなので、ここで書いた内容は事実とは大きく外れているものかもしれません。とにかく不明瞭な部分を自分なりに理由づけしたかっただけです。
サンプルコードを試される場合は、FirefoxのFireBugにあるコンソールに貼りつけて実行するか、Safariの開発ツールにあるコンソールに貼りつけて実行してください。それがわからない方は console.log の部分を alert に置き換えて確認してください。
話がややこしくなるので、今回はプロパティしか扱っていません。
名称の定義について
- オブジェクト
- データ構造を持った集合データのこと
- プロパティ
- オブジェクトに関連づられた変数のこと
- クラス
- インスタンス化できる関数オブジェクトのこと
- インスタンス
- コンストラクタを通して、クラスから生成されたオブジェクトのこと
- 疑似クラス
- インスタンス化できない関数オブジェクトのこと*1
- 疑似インスタンス
- 疑似クラスの実行を通して生成されたオブジェクトのこと
- 値
- データ構造を持たない単一のデータのこと
この定義が正しいかどうかはわかりませんが、同じように見えて、実は同じではないデータを区別して説明し、自分で納得するために、上記の名前の定義としました。仕様書を読んで選んだ言葉ではありませんのでご了承ください。
今回はプロパティを定義することを通して、JavaScriptのオブジェクトについて考察します。
- オブジェクトの中にプロパティを定義する
- クラスの中にプロパティを定義する
- インスタンスの中にプロパティを定義する
- 疑似クラスの中にプロパティを定義する
- 疑似インスタンスの中にプロパティを定義する
- prototypeプロパティの中にプロパティを定義する
JavaScriptはプロトタイプベースのオブジェクト指向言語なので、インスタンスの中にも、プロパティが定義できます。
サンプルコードと考察
ex1
var o = new Object(); o.prop = "私はプロパティです。"; console.log(o.prop); // 結果: // 私はプロパティです。
等価式。
var o = {}; o.prop = "私はプロパティです。"; console.log(o.prop); // 結果: // 私はプロパティです。
{} はオブジェクトを作るための簡易表現。オブジェクトリテラルと呼ぶ。
ex2
var o = { prop : "私はプロパティです。" }; console.log(o.prop); // 結果: // 私はプロパティです。
オブジェクトにプロパティを定義する時の書き方。いわゆるJSONと呼ばれるもの*2。
{ プロパティ名 : プロパティの値 }
プロパティが複数ある場合はカンマで区切る。
{ プロパティ1 : プロパティ1の値, プロパティ2 : プロパティ2の値, ... , プロパティn : プロパティnの値 }
ex3
var o = { prop : "私はプロパティです。" }; var obj = o; console.log(obj.prop); // 結果: // 私はプロパティです。
oはobjに代入されている。
ex4
変数にオブジェクトを代入した後、プロパティの値を変更してみる。
var o = { prop : "私はプロパティです。" }; var obj = o; obj.prop = "私は偽者です。"; console.log(o.prop); // 結果: // 私は偽物です。
objを更新するとoも更新される。オブジェクトの代入は、値渡しではなくて、参照渡し。
ex5
このままだと、以下のような処理ができない。
var o = { prop : "私はプロパティです。" }; var obj1 = o; var obj2 = o; obj1.prop = "ひとつめのオブジェクトです。"; obj2.prop = "ふたつめのオブジェクトです。"; // ひとつめのオブジェクトです。ふたつめのオブジェクトです。 // と出て欲しい。 console.log(obj1.prop + obj2.prop); // 結果: // ふたつめのオブジェクトです。ふたつめのオブジェクトです。
失敗。obj1もobj2も同一の存在になっている。このような時に、クラスが必要になる。クラスは設計書のようなもの。
ex6
var o = { prop : "私はプロパティです。" }; var obj = new o(); console.log(o.prop); // 結果: // o is not a constructor
オブジェクト = クラスではない。JavaScriptのクラスは関数オブジェクトで表現できる。クラス = 関数オブジェクト。上の定義通り言うと、クラス = インスタンス化できる関数オブジェクト。
ex7
クラスを作るには、最初の例(ex1)をObjectから、Functionにすれば良い。
var o = new Function(); o.prop = "私はプロパティです。"; console.log(o.prop); // 結果: // 私はプロパティです。
等価式。
function o() {}; o.prop = "私はプロパティです。"; console.log(o.prop); // 結果: // 私はプロパティです。
さらに等価式。
var o = function() {}; o.prop = "私はプロパティです。"; console.log(o.prop); // 結果: // 私はプロパティです。
最後の右辺に代入した function() {} は関数オブジェクトを作るための簡易表現。関数リテラルと呼ぶ。
ex8
var o = function() { this.prop = "私はプロパティです。";return this.prop }; console.log(o.prop); // 結果: // 私はプロパティです。
thisを使ってpropをoに関連づけている。
ごめんなさい、これはコメントのとおり間違い。コンストラクタを実行してないからundefined。
ex9
var o = function() { this.prop = "私はプロパティです。" }; var obj = o; console.log(obj.prop); // 結果: // undefined
thisを使って、oにpropを関連付けている。しかし、失敗。
ex10
ex6で失敗した、new演算子でインスタンスが作れるか試してみる。
var o = function() { this.prop = "私はプロパティです" }; var obj = new o(); console.log(obj.prop); // 結果: // 私はプロパティです。
成功。
ex9では、クラスを代入しただけで、インスタンスを作っていなかった。設計書を渡しただけで、実物を作ってなかった。だからundefinedだった。クラスの代入とインスタンスの代入は似ていてもまったく別物。
var obj = o;
これはインスタンスの代入。つまり設計書を元にした実物の代入。
var obj = new o();
インスタンスを代入する時の new hoge() という処理をコンストラクタと言う。インスタンスを代入するという言い方は一般的ではない。通常は、インスタンスを生成すると言う。インスタンスを生成する処理のことをコンストラクタと言う。
ex11
変数にインスタンスを代入した後、元のプロパティの値を変更してみる。
var o = function() { this.prop = "私はプロパティです。" }; var obj = new o(); o.prop = "私は偽者です。"; console.log(obj.prop); // 結果: // 私はプロパティです。
同じ設計書を使って作った実物は、見た目も性能も同じでも、それぞれ別の存在。同じ母ちゃんから生まれた、どこからどう見ても違いがわからない双子がいたとしても、ふたりは別人格。弟が童貞を卒業したからって、兄ちゃんが童貞を卒業したということにはならない。
ex12
ex5で失敗した処理に再挑戦する。ex5では、二つの変数の参照先が、同一だったため失敗していた。
var brother = function() { this.cherry = "はい!まだふたりとも童貞っす!" }; var nichan = new brother(); var otouto = new brother(); otouto.cherry = "童貞?違ぇよw兄貴と一緒にすんなwww"; console.log('兄:' + nichan.cherry + '弟:' + otouto.cherry); // 結果: // 兄:はい!まだふたりとも童貞っす!弟:童貞?違ぇよw兄貴と一緒にすんなwww
うん、別人格でしたね。お兄さんはもっと世間を知った方がいい。でもその前に、弟を一発殴っといた方がいいと思います。
ex13
var o = function() { this.prop = "私はプロパティです。" }; var obj = new o(); obj.prop2 = "私はプロパティ2です。"; console.log(obj.prop2); // 結果: // 私はプロパティ2です。
ex14
ex8と同じことを、疑似クラスと疑似インスタンスで実現してみる。まずは疑似クラスにプロパティを追加してみる。
var o = function() { return { prop : "私はプロパティです。" } }; console.log(o.prop); // 結果: // 私はプロパティです。
これもコメントいただいたとおり間違い。擬似インスタンス化してないからundefined。
ex15
var o = function() { return { prop : "私はプロパティです。" } }; var obj = o; console.log(obj.prop); // 結果: // undefined
失敗。
ex16
ex10と同様、new演算子でインスタンスが作れるか試してみる。
var o = function() { return { prop : "私はプロパティです。" } }; var obj = new o(); console.log(obj.prop); // 結果: // 私はプロパティです。
うまくいく。実は、この場合new演算子はいらない。oはクラスでないため、インスタンスは作れない。newをつけてもエラーが出ないだけで、instanceofで調べてもobjはoのインスタンスではない。これは、疑似クラスから作られた、疑似インスタンスと考えることにする。
var o = function() { return { prop : "私はプロパティです。" } }; var obj = o(); console.log(obj.prop); // 結果: // 私はプロパティです。
等価式。
var o = function() { return { prop : "私はプロパティです。" } }; var obj = o; console.log(obj().prop); // 結果: // 私はプロパティです。
さらに等価式。
var o = function() { return { prop : "私はプロパティです。" } }(); var obj = o; console.log(obj.prop); // 結果: // 私はプロパティです。
疑似クラスは丸括弧によって疑似インスタンス化できる。関数を実行するということ。上記の例の場合、どのタイミングで疑似インスタンス化しても良い。どのタイミングで疑似インスタンス化しても、結果は同じ。
疑似クラス。
o
疑似インスタンス。
o()
ex17
var o = function() { return { prop : "私はプロパティです。" } }; var obj = o(); obj.prop2 = "私はプロパティ2です。" console.log(obj.prop2); // 結果: // 私はプロパティ2です。
ex18
ex10のクラスとex16の値オブジェクトは、似ているが違う存在。
var o = function() { this.prop = "私はプロパティです。" }; var obj = new o(); console.log(obj instanceof o); // 結果: // true
ex10のクラスではtrue。
var o = function() { return { prop : "私はプロパティです。" } }; var obj = o(); console.log(obj instanceof o); // 結果: // false
ex16の疑似クラスではfalse。
前者のobjはoクラスのインスタンス、後者のobjは、o疑似クラスを疑似インスタンス化したもの。疑似インスタンスは疑似クラスのインスタンスではなく、あくまで疑似インスタンス。
まとめ
JavaScriptをオブジェクト指向言語として扱いたい場合、以下のどちらかを利用する。
言葉の定義は正しくないかもしれないけど、これらは同じように見えて違うものなので、区別して考えようというのが考察した結果です。
追記しました
多数の方にご覧いただき恐縮です。また、ブックマーク、コメント、ブコメ等ありがとうございます。
いただいたコメントやブコメの中でも言及されていましたが、prototypeプロパティを紹介していないのは問題だと思いましたので、本記事に追記することにします。prototypeプロパティを紹介しなかった理由はただひとつ、私がしっかり理解していないからです。試しながら、本を読みながら、理解をしつつ追記していきます。
ex19
prototypeプロパティを使って、プロパティを定義する。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; console.log(o.prop); // 結果: // undefined
ex20
試しにoの中を確認してみる。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; for (var obj in o) console.log(obj); // 結果: // prototype
では、直接アクセスするとどうなるか。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; console.log(o.prototype.prop); // 結果: // 私はプロパティです。
成功。プロパティ自体、存在はしているが、o.propで呼び出すためにはコンストラクタインスタンス化が必要ということがわかる。
ex20
prototypeプロパティを使って、プロパティを定義し、変数にインスタンスを代入する。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; var obj = new o(); console.log(obj.prop); // 結果: // 私はプロパティです。
コンストラクタを実行するとうまくいく。
ex19と同様、直接アクセスするとどうなるか。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; var obj = new o(); console.log(obj.prototype.prop); // 結果: // obj.prototype is undefined
ex19で直接アクセスできることが確認できたが、コンストラクタを実行すると、インスタンスの中にはprototypeプロパティが存在しなくなることがわかる。
ex21
試しにobjの中を確認してみる。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; var obj = new o(); for (var p in obj) console.log(p); // 結果: // prop
インスタンスの中にはprototypeは存在せず、propのみが存在している。ex20でエラーになった理由が証明できた。
ex22
変数にインスタンスを代入した後、インスタンスのプロパティを変更する。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; var obj = new o(); obj.prop = "私は更新されたプロパティです。"; console.log(obj.prop); // 結果: // 私は更新されたプロパティです。
更新される。では、prototypeプロパティの方はどうか。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; var obj = new o(); obj.prop = "私は更新されたプロパティです。"; console.log(o.prototype.prop); // 結果: // 私はプロパティです。
更新されていない。インスタンスの更新はprototypeの方には反映されない。
ex23
変数にインスタンスを代入した後、prototypeプロパティの、プロパティを変更する。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; var obj = new o(); o.prototype.prop = "私は更新されたプロパティです。"; console.log(obj.prop); // 結果: // 私は更新されたプロパティです。
更新される。しかし、これは奇妙に感じる。ex21でobjにはprototypeプロパティは存在せず、propのみが存在していることを確認したからだ。しかしながら、今、確かにprototypeプロパティへの変更はインスタンスに反映された。つまり、見えなくなってはいるものの、関係は確かに残っている。これがprototypeプロパティの奇異な特徴になる。
ex24
変数にインスタンスを代入した後、インスタンスのプロパティを変更し、その後、prototypeプロパティの、プロパティを変更する。
var o = function() {}; o.prototype.prop = "私はプロパティです。"; var obj = new o(); obj.prop = "私は更新されたプロパティです。"; o.prototype.prop = "私はさらに更新されたプロパティです。"; console.log(obj.prop); // 結果: // 私は更新されたプロパティです。
先ほどと同様、最後にprototypeプロパティの変更をしているにも関わらず、反映されていないことが確認できる。つまり、JavaScriptのプロパティは以下の優先順位で採用が決まると言うことがわかる。
インスタンスのプロパティの変更 > prototypeプロパティのプロパティの変更
prototypeプロパティのプロパティは、インスタンスのデフォルト値と考えることができる。これがJavaScriptにおける継承の正体になる。
- Cli@ - JavaScriptのオブジェクトについて考察してみた - あと味
- Cli@ - JavaScriptのオブジェクトについて考察してみた - あと味
- 院日誌 - [http://d.hatena.ne.jp/jdg/20090612/1244765780:title]
- [crowbar]JavaScriptのオブジェクト指向をcrowbarから考える(その1)
- [crowbar]JavaScriptのオブジェクト指向をcrowbarから考える(その2)
- あと味 - JavaScriptのnewって本当にいらない子?
- sobabanのNullい日々。 - javascript のobject
- 3066 http://gigazine.net/index.php?/news/comments/20090612_headline/
- 1176 http://blog.livedoor.jp/dankogai/archives/51223538.html
- 1011 http://d.hatena.ne.jp/
- 814 http://b.hatena.ne.jp/hotentry
- 741 http://reader.livedoor.com/reader/
- 446 http://b.hatena.ne.jp/hotentry/it
- 373 http://alfalfa.livedoor.biz/archives/51477091.html
- 350 http://blog.livedoor.jp/dankogai/
- 336 http://phpspot.org/blog/archives/2009/06/2009612.html
- 243 http://www.google.co.jp/reader/view/


