プロトタイプについて整理でもしてみる

はいどーもニートです。
人生詰んでるので、死ぬ前に備忘録を兼ねてJavaScriptにまつわる用語の整理(および得た知識の整理)の続きでもしてみたいと思います。
「おめぇそれちげぇよ!」というツッコミは歓迎ですが、ツッコンでるころには死んでるので意味ないです。

注*冗談なので真に受けて通報しないでください。

前回の記事でプロトタイプについて少し書きましたが、プロトタイプの動きはややこしいので、さらにまとめてみます。
ついでにオブジェクトとインスタンスの関係についても最後に触れておきました。

プロトタイプ君

はいまずクラスを作ってみましょう。

var Hoge = function(name, age){
this.name = name;
this.age = age;
};

ほげクラス君のできあがりです。
これにプロトタイプを追加しましょう。

Hoge.prototype.getStatus = function() {
console.log('名前:' + this.name);
console.log('年齢' + this.age + '歳');
};

ではインスタンスを作ってみます。

var test = new Hoge('ほげ君', 1200);

これでHogeクラスのtestインスタンスができました。
さっそく使ってみましょう。

test.getStatus();
//名前:ほげ君
//年齢:1200歳

さてtestインスタンスを作りましたが、Hogeクラス直下にはgetStatusメソッドなんてありませんよね。
Hoge.prototypeプロパティにgetStatusメソッドがあるだけです。
これはtestインスタンスの中でどのように参照されているのでしょうか?

「test.prototypeってのを使ってるんだろ?」と思った方は残念。
testインスタンスにprototypeというプロパティは存在しません
実際、

console.log(test.prototype);

とやってみてください。
undefinedが返ってくるはずです。

ではどうやってアクセスしているのか。
実はこうです。

test.__proto__.getStatus();

プロトタイプチェーンをさかのぼる時はこの__proto__を使うことになっていて、コンストラクタ関数のprototypeプロパティにアクセスしているのですね。
実際、test.__proto__.getStatusとHoge.prototype.getStatusは同じ参照先を示します

console.log(test.__proto__.getStatus === Hoge.prototype.getStatus);
//true

でも実際のコードを書くときに

test.__proto__.getStatus();

と呼ぶのは微妙です。
まずそもそもの話ですが、__proto__が使えない環境もあるらしいです(この辺は自分では未確認。ツクールは使えました)。
そして、上記のコードを実行すればわかると思いますが、「名前:undefined」などと返ってきます。
これを避けるために

test.__proto__.getStatus.call(this);

としてもよいですが、無意味な上に冗長なので、特に理由がなければ素直に

test.getStatus();

と呼ぶのがよいでしょう。

他にもプロトタイプはぶった切ったり、つなげたりもできるようなのですが、実際に僕が使うことはないだろうなーと思うので、とりあえずはこのへんで。
一応コードをまとめて載せておきます。


var Hoge = function(name, age){
this.name = name;
this.age = age;
};

Hoge.prototype.getStatus = function() {
console.log('名前:' + this.name);
console.log('年齢' + this.age + '歳');
};

Hoge.prototype.helloHoge = function() {
console.log('こんにちは' + this.name);
};

var test = new Hoge('ほげ君', 1200);

test.getStatus();
//名前:ほげ君
//年齢:1200歳
console.log(test.__proto__);

test.__proto__.getStatus();//名前などにundefinedの値が入ってしまう
test.__proto__.getStatus.call(this);//こっちは正常
console.log(test.prototype);//存在しない
console.log(test.__proto__.getStatus === Hoge.prototype.getStatus);//true
console.log(test.__proto__.helloHoge === Hoge.prototype.getStatus);//false
console.log(Hoge.__proto__ === Function.prototype);//true

オブジェクトとインスタンスの違い

おまけでオブジェクトとインスタンスの違いもまとめておきます。

JavaScriptではプリミティブ型以外はほぼ全てオブジェクトと呼べます。
インスタンスもオブジェクトです。
というか、JavaScriptにおいては、ほぼ全てのオブジェクトはObjectオブジェクトのインスタンスです。
じゃあ何でインスタンスとオブジェクトという二つの言い方があるのでしょうか?
全部オブジェクトでええやんという話にはならないのでしょうか?

例えば動物を考えてみてください。
動物という言葉であらゆる動物を指すよりも、哺乳類だとか魚類だとか鳥類だとかに区分した方がわかりやすいですよね。
鳥類でも各種類によって名前がわけられてます。
それと一緒です(多分)。

というわけで、オブジェクトはインスタンスになる可能性がありますし、インスタンスはオブジェクトです。
これはちょうど、動物は鳥類になる可能性があるし、鳥類は動物である関係と一緒です。
ついでに言うと、JavaScriptではオブジェクトは関数になる可能性がありますし、関数はオブジェクトです。

で、具体的にインスタンスってなんやねんということですが、クラスから作られたオブジェクトのことです。
言い換えると、コンストラクタ関数とnew演算子を使って作られたオブジェクトのことです。

var Hoge = function(){};
var test = new Hoge();

このコードならtestがインスタンスです。

ちなみにですが、testは入れ物なだけで、これ自体がインスタンスではないです。
「え、でもお前、今まで散々testインスタンスとか言っとったやんけ」と思うかもしれませんが、本当はtestはインスタンスじゃないんです。
つまり変数testは名無しのHogeインスタンスの参照の入れ物になっているだけです。
実際、testに別のインスタンスの参照先を入れても、名無しのHogeインスタンス自体が消えるわけではありません(他にどこからも参照されてなければガベージコレクションで消えますが)。
が、いちいちこれらのことを意識して説明するのは面倒なので、普通はtestのことをHogeクラスのtestインスタンスと呼びます

ほな、こんなところで。

フォローする