JavaScriptのプロトタイプベースの継承の良い例


89

私はOOP言語で10年以上プログラミングを行っていますが、現在JavaScriptを学習しています。プロトタイプベースの継承に出会ったのはこれが初めてです。私は良いコードを勉強することで最も速く学ぶ傾向があります。プロトタイプ継承を適切に使用するJavaScriptアプリケーション(またはライブラリ)の適切に記述された例は何ですか?また、プロトタイプの継承がどのように/どこで使用されているのか(簡単に)説明できますか?


1
そのベースライブラリをチェックする機会がありましたか?本当にいいですし、かなり小さいです。よければ、私の答えを答えとしてマークすることを検討してください。TIA、ローランド。
Roland Bouman、2010年

私はあなたと同じ船に乗っていると思います。私も、このプロトタイプ言語について少し学びたいと思います。oopフレームワークなどに限定されているわけではなく、それらがすばらしく、すべてであっても、学ぶ必要があります。使用するつもりでも、いくつかのフレームワークだけが私のためにそれを行います。しかし、新しい方法で新しい言語で新しいものを作成する方法を学び、箱から出して考えてください。私はあなたのスタイルが好きです。私は私を助けようとするでしょう、そしておそらくあなたを助けます。何か見つけたらすぐにお知らせします。
marcelo-ferraz、2010

回答:


48

Douglas Crockfordは、JavaScriptプロトタイプ継承に関する素晴らしいページを持っています。

5年前、私はJavaScriptで古典的継承を書きました。JavaScriptはクラスフリーのプロトタイプ言語であり、古典的なシステムをシミュレートするのに十分な表現力があることを示しました。それ以来、私のプログラミングスタイルは進化しています。私はプロトタイプ主義を完全に受け入れることを学び、古典的なモデルの制限から自分自身を解放しました。

Dean EdwardのBase.jsMootoolsのClass、またはJohn ResigのSimple Inheritanceは、JavaScriptで従来の継承を行う方法です。


単にnewObj = Object.create(oldObj);クラスフリーにしたいだけなのですか?それ以外の場合oldObjは、コンストラクター関数のプロトタイプオブジェクトで置き換えますか?
Cyker

76

先に述べたように、ダグラス・クロックフォードの映画は、その理由とその方法をカバーする良い説明をしています。しかし、それを数行のJavaScriptに入れるには:

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

ただし、この方法の問題は、オブジェクトを作成するたびにオブジェクトが再作成されることです。別の方法は、次のようにプロトタイプスタックでオブジェクトを宣言することです。

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

イントロスペクションに関しては、若干のマイナス面があります。testOneをダンプすると、有用な情報が少なくなります。また、「testOne」のプライベートプロパティ「privateVariable」はすべてのインスタンスで共有されます。これらは、shesekからの返信で役立ちます。


3
TESTONEでそれを行うノートprivateVariable単にのスコープ内の変数で生命維持、そしてあなたがそれにインスタンス固有のデータを格納してはならないように、その、すべてのインスタンス間で共有。(testTwoでは、testTwo()を呼び出すたびに新しいインスタンスごとのスコープが作成されるため、インスタンス固有です)
shesek

あなたが他のアプローチを示し、それがコピーを作成するのでそれを使わない理由
Murphy316

毎回オブジェクトを再作成する問題は、主に新しいオブジェクトごとにメソッドが再作成されるためです。ただし、でメソッドを定義することで問題を軽減できDog.prototypeます。したがってthis.bark = function () {...}、を使用する代わりにDot.prototype.bark = function () {...}Dog関数の外で行うことができます。(この回答の詳細を参照してください)
Huang Chao

26
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);

3
おそらく、あなたの答えをこのリンクを追加すると、さらに多くの絵を完成されることがありますdeveloper.mozilla.org/en/docs/Web/JavaScript/Reference/...
Dynom

14

私はYUIとディーン・エドワードのBaseライブラリを見てみましょう:http : //dean.edwards.name/weblog/2006/03/base/

YUIの場合は、langモジュール espを簡単に確認できます。YAHOO.lang.extendの方法。次に、一部のウィジェットまたはユーティリティのソースを参照し、それらがそのメソッドをどのように使用するかを確認できます。


YUI 2は2011年に廃止されたため、へのリンクlangは半壊されています。誰もがYUI 3の修正に関心がありますか?
応答

yui 3のlangには、extendメソッドがないようです。しかし、答えは例として実装を使用することを意図しているので、バージョンは重要ではありません。
eMBee


4

これは、Mixuのノードブック(http://book.mixu.net/node/ch6.html)から見つけた最も明確な例です。

継承よりも構成を優先します。

構成-オブジェクトの機能は、他のオブジェクトのインスタンスを含むことにより、さまざまなクラスの集合から構成されます。継承-オブジェクトの機能は、それ自体の機能とその親クラスの機能で構成されます。継承が必要な場合は、古いJSを使用してください

継承を実装する必要がある場合は、少なくともさらに別の非標準の実装/マジック関数の使用を避けてください。純粋なES3で継承の合理的な複製をどのように実装できるかを以下に示します(プロトタイプでプロパティを定義しないというルールに従う限り)。

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

これは従来の継承と同じものではありませんが、標準で理解可能なJavascriptであり、人々が主に求める機能、つまりチェーン可能なコンストラクターとスーパークラスのメソッドを呼び出す機能を備えています。


4

ES6 classおよびextends

ES6 classとは、extends以前に可能でプロトタイプチェーン操作のためだけの構文糖、およびので間違いなく最も標準的な設定です。

最初に、プロトタイプチェーンと.プロパティルックアップの詳細をご覧くださいhttps : //stackoverflow.com/a/23877420/895245

次に、何が起こるかを分解してみましょう。

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// /programming/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

すべての定義済みオブジェクトを含まない簡略図:

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype


1

私が見た中で最も良い例は、ダグラス・クロックフォードのJavaScript:The Good Partsです。言語についてバランスのとれた見方をするのを助けるために買うことは間違いなく価値があります。

Douglas CrockfordはJSON形式を担当し、JavaScriptの第一人者としてYahooで働いています。


7
責任者?それは、ほぼ「有罪」のように聞こえます:)
Roland Bouman 2010年

@Roland JSONはデータを保存するための非常に優れた非冗長形式だと思います。彼は間違いなくそれを発明しなかった、2002年のSteamの構成設定用のフォーマット
Chris S

Chris S、私もそう思います-交換フォーマットとしてXMLをすべてスキップして、JSONにすぐに移動できたらいいのにと思っています。
Roland Bouman、2010年

3
あまり発明する:JSONは周りの1997年以来の言語になっているJavaScriptの独自のオブジェクトリテラル構文のサブセットである
ティム・ダウン

@Timeの良い点-最初から存在していることに気づかなかった
Chris S


0

Javascriptでプロトタイプベースの継承の例を追加します。

// Animal Class
function Animal (name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function (amount) {
  console.log(this.name, "eating. Energy level: ", this.energy);
  this.energy += amount;
  console.log(this.name, "completed eating. Energy level: ", this.energy);
}

Animal.prototype.sleep = function (length) {
  console.log(this.name, "sleeping. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}

Animal.prototype.play = function (length) {
  console.log(this.name, " playing. Energy level: ", this.energy);
  this.energy -= length;
  console.log(this.name, "completed playing. Energy level: ", this.energy);
}

// Dog Class
function Dog (name, energy, breed) {
  Animal.call(this, name, energy);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  console.log(this.name, "barking. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done barking. Energy level: ", this.energy);
}

Dog.prototype.showBreed = function () {
  console.log(this.name,"'s breed is ", this.breed);
}

// Cat Class
function Cat (name, energy, male) {
  Animal.call(this, name, energy);
  this.male = male;
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function () {
  console.log(this.name, "meowing. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done meowing. Energy level: ", this.energy);
}

Cat.prototype.showGender = function () {
  if (this.male) {
    console.log(this.name, "is male.");
  } else {
    console.log(this.name, "is female.");
  }
}

// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();

const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();

ES6は、コンストラクターとスーパーキーワードを使用することで、はるかに簡単に継承を実装できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.