ブログトップ 記事一覧 ログイン 無料ブログ開設

あと味 このページをアンテナに追加 RSSフィード Twitter

2009-06-12 JavaScriptのオブジェクトについて考察してみた

JavaScriptのオブジェクトについて考察してみた

JavaScript勉強しているとオブジェクトとはなんぞや?ということがわからなくなってきます。選択肢が増えれば増えるほど。

JavaScriptには、同じように見えて、実は同じではないデータがあります。それらのオブジェクトについて、区別して説明が付けられるように、自分なりに考察してみました。勉強中のアウトプットなので、ここで書いた内容は事実とは大きく外れているものかもしれません。とにかく不明瞭な部分を自分なりに理由づけしたかっただけです。

サンプルコードを試される場合は、FirefoxFireBugにあるコンソールに貼りつけて実行するか、Safariの開発ツールにあるコンソールに貼りつけて実行してください。それがわからない方は console.log の部分を alert に置き換えて確認してください。

話がややこしくなるので、今回はプロパティしか扱っていません。

名称の定義について

オブジェクト
データ構造を持った集合データのこと
プロパティ
オブジェクトに関連づられた変数のこと
クラス
インスタンス化できる関数オブジェクトのこと
インスタンス
コンストラクタを通して、クラスから生成されたオブジェクトのこと
疑似クラス
インスタンス化できない関数オブジェクトのこと*1
疑似インスタンス
疑似クラスの実行を通して生成されたオブジェクトのこと
データ構造を持たない単一のデータのこと

この定義が正しいかどうかはわかりませんが、同じように見えて、実は同じではないデータを区別して説明し、自分で納得するために、上記の名前定義としました。仕様書を読んで選んだ言葉ではありませんのでご了承ください。

今回はプロパティ定義することを通して、JavaScriptオブジェクトについて考察します。

プロパティ定義する方法は以下の56通りです。

  1. オブジェクトの中にプロパティ定義する
  2. クラスの中にプロパティ定義する
  3. インスタンスの中にプロパティ定義する
  4. 疑似クラスの中にプロパティ定義する
  5. 疑似インスタンスの中にプロパティ定義する
  6. 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

new演算子インスタンスが作れるか試してみる。

var o = { prop : "私はプロパティです。" };
var obj = new o();
console.log(o.prop);

// 結果:
// o is not a constructor

oはコンストラクタではないというエラー

オブジェクト = クラスではない。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);

// 結果:
// 私はプロパティです。

oを更新してもobjは更新されない。

同じ設計書を使って作った実物は、見た目も性能も同じでも、それぞれ別の存在。同じ母ちゃんから生まれた、どこからどう見ても違いがわからない双子がいたとしても、ふたりは別人格。弟が童貞卒業したからって、兄ちゃんが童貞卒業したということにはならない。

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

ex9と同様、変数に疑似クラスを代入してみる。

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オブジェクト指向言語として扱いたい場合、以下のどちらかを利用する。

  1. クラス + インスタンス
  2. 疑似クラス + 疑似インスタンス

言葉定義は正しくないかもしれないけど、これらは同じように見えて違うものなので、区別して考えようというのが考察した結果です。

追記しました

多数の方にご覧いただき恐縮です。また、ブックマークコメントブコメ等ありがとうございます。

いただいたコメントブコメの中でも言及されていましたが、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

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における継承の正体になる。

chikurachikura 2009/06/12 11:47 twitterでも書いたけど、いちおうまとめて。
こういう記事は、JavaScript初学者の理解の助けになると思います。素晴らしいですね。

いくつか気づいた点を指摘すると、ex8とex14は、undefinedになるはずです。

また、「擬似クラス」という用語はCSSで使われていて紛らわしいのでやめた方がいいかも?
単にオブジェクトを返す関数というだけなので、特別な呼び方は不用な気もします。

また、上記で言う擬似クラスを使って生成されたものも、他のインスタンスとなんら変わらないインスタンスなので、「擬似インスタンス」という呼び方は、聞く人に何か誤解を与えるかもしれません。

押さえるべきポイントは、new演算子の際に何が行われるか?だと思うので、こちらのページもぜひ読んでみてください。

JavaScript の new 演算子の意味: Days on the Moon
http://nanto.asablo.jp/blog/2005/10/24/118564

chikurachikura 2009/06/12 14:30 記事修正ありがとうございました。用語については、jdgさんの独自用語だとちゃんと書かれているわけですし、それを踏まえて読めば良いだけでしたね。(^^;

JavaScriptにはクラスという機能はなくて、JavaやC++のクラスのような見かけを実現する為のコンストラクタ関数とnew演算子の組み合わせがある為に、ちょっとややこしいことになってます。

この記事のように一つ一つ検証していくと、より理解が深まって良いですよね!

jdgjdg 2009/06/12 14:33 > chikuraさん
コメントでご指摘ありがとうございます!あとその後いろいろ教えていただいて大変たすかりました。

ex8とex14の件は、検証していなかったみたいです。

擬似クラスという名称はCSSと同じで紛らわしいのは確かですね。でもそれを言い出すと、クラスも同じってことになるので、このままいきたいと思います。

擬似インスタンスについてはincetance_name of object_nameでfalseが返ってくるので、インスタンスと区別しました。

もう少しこの部分の深い理解は必要そうですね。ご指摘ありがとうございました!

暇人暇人 2009/06/12 18:56 ex7の3つは等価式ではないです。上と下はあまり変わりませんが、真ん中は致命的に違っていて、初期化のタイミングが変わってきます。

それと、擬似クラスの考え方は、クラスを作るというよりはFactoryメソッドの考え方に近い気がしますね。オブジェクト生成を担当する関数を作ってるだけなので。

ついでに言うと、クラスを作りたい場合は関数オブジェクトのプロトタイプをいじるのが昨今の主流だと思われます。

jdgjdg 2009/06/12 19:44 > 暇人さん
ご指摘ありがとうございます。
ex7は等価式ではないんですね。誰かの記事で拝見した気がするんですが、そこまで細かい仕様把握できていません。。。

Factoryメソッドについては他の方からも指摘があったのですが、デザインパターンをまじめに勉強したことないので、まったくその考えには至りませんでした。

prototypeプロパティについては、この記事で決定的に抜けているところですね。それについては後日追記するようにします。

hatesatebloghatesateblog 2009/06/13 12:35 これはすごくわかりやすいですね。
参考にします。

chaichanPaPachaichanPaPa 2009/06/13 13:08 >でもその前に、弟を一発殴っといた方がいいと思います
つぼに、はまりました!
しかし、JavaScriptは、Perlと同じぐらい難解な言語ですね!

jdgjdg 2009/06/13 15:24 > hatesateblogさん
ありがとうございます!そう言っていただけるとうれしいです!

jdgjdg 2009/06/13 15:27 > chaichanPaPaさん
Perlのことで調べてる時に何度かblogを拝見したことがあります!

PerlもJavaScriptも難解ですね。でも日本人はそこにひかれる何かがあるんだと思っています。と、謎めいたことを言いつつ後日そのようなエントリーを書く予定です。

MasaHeroMasaHero 2009/06/13 16:57 >インスタンスを生成することをコンストラクタと言う。
それはコンストラクトで、コンストラクトを行うものをコンストラクタと呼ぶのでは。

jdgjdg 2009/06/13 17:09 > MasaHeroさん
ご指摘ありがとうございます!コンストラクトという動詞があるんですね。初めて知りました。該当箇所を一部修正しました。

hoshikuzuhoshikuzu 2009/06/16 12:55 ex2で、「いわゆるJSON と呼ばれるもの」とのご説明がありますが、首をかしげました。わかりやすい例をあげますと、JSONでは、{"prop":"私はプロパティです。"}と書けますが、{prop:"私はプロパティです。"}とは書けません。その他、JSONには細かい仕様があるはずですので、必要に応じて適宜調べてみてください。

jdgjdg 2009/06/16 16:29 > hoshikuzuさん
ご指摘ありがとうございます。脚注を追記しました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証