元ねた
JavaScriptのプロトタイプからオブジェクト指向を学ぶ
・prototypeを使った例
moto1.jsfunction Dog(cry) { this.cry = cry; } Dog.prototype.bark = function() { console.log(this.cry); } var dog = new Dog('bow'); dog.bark();
こう書き換えると・・
・プロトタイプっぽい何か
function Cat(cry) {
this.cry = cry;
}
var proto = {}
proto.bark = function() {
console.log(this.cry);
}
var cat = new Cat('mew');
cat.bark = proto.bark;
cat.bark(); // 'mew'
var cat2 = new Cat('mew mew');
cat2.bark = proto.bark;
cat2.bark(); // 'mew mew'
prototypeとただのオブジェクト(コード例のproto)は、どこが違うの?
・prototypeの場合は、なければ親を探しに行ってくれる
(親を設定した場合に限る。コード例(ex2)の、ExDog.prototype = Object.create(Dog.prototype);
の部分。)
・prototypeを使うと、コードが数行減る
(コード例(ex1)の、var proto = {}
とcat.bark = proto.bark;
の部分)
function ExDog(cry)
{
this.cry = cry;
}
ExDog.prototype = Object.create(Dog.prototype);
var exDog = new ExDog('bow wow');
exDog.bark();
この例では、ExDog.barkは存在しないので、コード例(moto1)のDog.barkを探しに行く。
そこにも無ければ、Object.barkを探し・・その先はチェーンの末端なので見つからないと怒られる。
ポイント
・(自動的な)プロパティの継承を考えるなら、prototypeを使う。
ただし、プロトタイプチェーンが長くなると、見つからない場合は深い所まで探しに行かないといけないので、パフォーマンスの低下につながる場合がある。
参考資料
MDN - 継承とプロトタイプチェーン
や...やっと理解できた!JavaScriptのプロトタイプチェーン
MDN - Function プロトタイプオブジェクト
目を通しておいた方がよいページ
MDN - Object.prototype.__proto__
蛇足
・プロトタイプもどきで、継承と似たようなことをやろうとすると・・
function ExCat(cry)
{
this.cry = cry;
}
var exProto = Object.create(proto);
var exCat = new ExCat('みゅみゅ?');
exCat.bark = exProto.bark;
exCat.bark(); // 'みゅみゅ?'
面倒!(というより、色々書くのを忘れそう)
(実際は、もう少し楽な書き方ができますが・・)
蛇足2:ありがちな失敗
書く順番を間違える
~~is not foundや、~~is not a functionなどのエラーが表示されるパターン。
(エラーが表示される場合は)使われる側を、使う側より前に書きましょう。(他の言語で言う所の、プロトタイプ宣言みたいな感じで)
参考:
MDN - 関数宣言の巻き上げ
失敗例 (da2-1.js)
・cat.f1()
は、Cat.prototype.f1
より後に置く必要がある。
・f1()
がf2()
を呼ぶので、f2()
、f1()
の順に並べる必要がある。
var cat = new Cat('wild cat'); // Ok
cat.f1(); // TypeError: cat.f1 is not a function (prototype.f1より前にあるのでエラー)
function Cat(cry) {
this.cry = cry;
}
Cat.prototype.f1 = function() {
console.log('f1');
this.f2(); // TypeError: this.f2 is not a function
}
cat.f1(); // f1()は呼べる。ただし、`this.f2()`がエラーを表示する。
Cat.prototype.f2 = function() {
console.log('f2');
}
失敗例 (da2-2.js)
・Cat(cry)
の中で、Bird.prototype.chain1
が必要になるので、new Cat('wild cat')
はBird.prototype.chain1
より後に置く必要がある。
function Bird(cry) {
this.cry = cry;
}
var cat = new Cat('wild cat'); // TypeError: (intermediate value).chain1 is not a function
Bird.prototype.chain1 = function() {
console.log('chain1');
return this;
}
var cat2 = new Cat('wild cat2'); // ここなら大丈夫。
function Cat(cry) {
this.cry = cry;
this.bird = new Bird('I can fly!')
.chain1();
}
チェーンの罠にはまる
~~is undefinedなどのエラーが表示されるパターン。
(this自体が別物か、プロパティが作られる前にアクセスした場合などに発生します)
※これは、プロトタイプの罠というよりも、チェーンを使う場合のポイントです。
(でも、プロトタイプだからthisの扱いで間違えた?と考えやすい所。)
JavaScriptでは、オブジェクトの中身(プロパティ)を後から増やせるので、いつ増えたのかを気にしながら使いましょう。
MDN - オブジェクトとそのプロパティ、
MDN - object の型に対してプロパティを定義するなどを参照。
※参照先の前者はthis、後者はprototypeについての説明なので注意しましょう。
※thisが指す先がライブラリだった場合は・・怖いことに。(ライブラリ内部で使用している名前と重複した場合など)
失敗例 (da2-3.js) thisの中身に注意しよう!
// da2-2を少し改造。
function Bird(cry) {
this.cry = cry;
}
Bird.prototype.chain1 = function() {
console.log('chain1');
return this;
}
Bird.prototype.chain2 = function(self) {
var bird = self.bird; // birdはundefinedになる
console.log(bird.cry); // TypeError: bird is undefined
return this;
}
var cat = new Cat('wild cat');
function Cat(cry) {
this.cry = cry;
this.bird = new Bird('I can fly!')
.chain1()
.chain2(this); // この段階では、this.birdはセットされていない
}
こうすれば、期待通りの動作に。(da2-3-ok.js)
・チェーンを切り離す
function Cat(cry) {
this.cry = cry;
this.bird = new Bird('I can fly!'); // this.birdがセットされる
this.bird.chain1()
.chain2(this);
}