JavaScriptの古典的な継承とプロトタイプの継承


118

私は非常に多くのリンクをググっていて、古典的な継承とプロトタイプの継承の違いについて良い考えを得ることができませんか?

私はこれらからいくつかのことを学びましたが、私はまだ概念について混乱しています。

古典的遺産

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

//superclass method
Shape.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
    console.info("Shape moved.");
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); //call super constructor.
}

//subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);

古典的継承は内部でプロトタイプ継承を使用しますか?

http://aaditmshah.github.io/why-prototypal-inheritance-matters/

上記のリンクから、古典的な継承では実行時に新しいメソッドを追加できないことがわかりました。これは正しいです?しかし、上記のコード確認すると、実行時にプロトタイプを通じて「移動」メソッドと任意のメソッドを追加できます。これはプロトタイプベースの古典的な継承ですか?もしそうなら、実際の古典的継承とプロトタイプ継承は何ですか?私はそれについて混乱しています。

プロトタイプの継承。

function Circle(radius) {
    this.radius = radius;
}
Circle.prototype.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};
Circle.prototype.circumference: function () {
    return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);

これは古典的な継承に似ていますか?プロトタイプ継承とは何ですか?古典的継承とは何ですか?古典的な継承はなぜ悪いのですか?

これらを簡単に理解するための簡単な例を教えてください。

おかげで、

シヴァ


:このチェックアウト、重複stackoverflow.com/questions/1595611/...
Silviu Burcea

5
ないあなたがおよそここにいる何を-コードの最初のブロックがあるプロトタイプの継承、古典ではありません。2番目のコードブロックには継承がまったくありません。
Alnitak 2013年

これはxplainことがあります。blog.stephenwyattbush.com/2012/05/01/...を
HasanAboShally

@alnitak developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... このリンクは1つが、古典的な継承したことを伝えます。それが混乱している理由です。
SivaRajini 2013年

古典的な継承を避けたい理由の詳細については、私の講演「古典的な継承は廃止された:プロトタイプOOでの考え方
Eric Elliott

回答:


248

質問で示した両方のコードサンプルは、プロトタイプの継承を利用しています。実際、JavaScriptで作成するオブジェクト指向コードはすべて、プロトタイプ継承のパラダイムです。JavaScriptには従来の継承はありません。これにより、問題が少し解消されます。

                                   Inheritance
                                        |
                         +-----------------------------+
                         |                             |
                         v                             v
                    Prototypal                     Classical
                         |
         +------------------------------+
         |                              |
         v                              v
Prototypal Pattern             Constructor Pattern

ご覧のとおり、プロトタイプ継承とクラシック継承は、継承の2つの異なるパラダイムです。Self、Lua、JavaScriptなどの一部の言語は、プロトタイプの継承をサポートしています。ただし、C ++、Java、C#などのほとんどの言語は、従来の継承をサポートしています。


オブジェクト指向プログラミングの概要

プロトタイプ継承とクラシック継承はどちらもオブジェクト指向プログラミングのパラダイムです(つまり、オブジェクトを扱います)。オブジェクトは、実際のエンティティのプロパティをカプセル化した単なる抽象化です(つまり、オブジェクトはプログラム内の実際の単語を表します)。これは抽象化として知られています。

抽象化:コンピュータプログラムでの現実世界のものの表現。

理論的には、抽象化は「特定の例から共通の特徴を抽出することによって形成される一般的な概念」として定義されます。ただし、この説明のために、代わりに前述の定義を使用します。

現在、いくつかのオブジェクトには多くの共通点があります。例えば、マッドバイクとハーレーダビッドソンには多くの共通点があります。

マッドバイク:

マッドバイク。

ハーレーダビッドソン:

ハーレーダビッドソン

マッドバイクとハーレーダビッドソンはどちらもバイクです。したがって、バイクはマッドバイクとハーレーダビッドソンの両方を一般化したものです。

                   Bike
                     |
    +---------------------------------+
    |                                 |
    v                                 v
Mud Bike                       Harley Davidson

上記の例では、バイク、マッドバイク、ハーレーダビッドソンはすべて抽象化されています。ただし、自転車は、マッドバイクとハーレーダビッドソンのより一般的な抽象化です(つまり、マッドバイクとハーレーダビッドソンの両方が特定の種類の自転車です)。

一般化:より具体的な抽象化の抽象化。

オブジェクト指向プログラミングでは、オブジェクト(実世界のエンティティの抽象化)を作成し、クラスまたはプロトタイプを使用して、これらのオブジェクトの一般化を作成します。汎化は継承によって作成されます。バイクはマッドバイクを一般化したものです。したがって、泥自転車は自転車から継承します。


古典的なオブジェクト指向プログラミング

従来のオブジェクト指向プログラミングでは、クラスとオブジェクトの2種類の抽象化があります。前述のように、オブジェクトは実世界のエンティティを抽象化したものです。一方、クラスはオブジェクトまたは別のクラスの抽象化です(つまり、それは一般化です)。たとえば、次のことを考慮してください。

+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity |                Comments               |
+----------------------+----------------+---------------------------------------+
| 0                    | John Doe       | Real World Entity.                    |
| 1                    | johnDoe        | Variable holding object.              |
| 2                    | Man            | Class of object johnDoe.              |
| 3                    | Human          | Superclass of class Man.              |
+----------------------+----------------+---------------------------------------+

古典的なオブジェクト指向プログラミング言語でわかるように、オブジェクトは抽象化(つまり、すべてのオブジェクトの抽象化レベルは1)であり、クラスは一般化(つまり、すべてのクラスの抽象化レベルは1より大きい)です。

従来のオブジェクト指向プログラミング言語のオブジェクトは、クラスをインスタンス化することによってのみ作成できます。

class Human {
    // ...
}

class Man extends Human {
    // ...
}

Man johnDoe = new Man();

古典的なオブジェクト指向プログラミング言語の要約では、オブジェクトは実世界のエンティティの抽象化であり、クラスは一般化(つまり、オブジェクトまたは他のクラスの抽象化)です。

したがって、抽象化のレベルが上がるとエンティティはより一般的になり、抽象化のレベルが下がるとエンティティはより具体的になります。この意味で、抽象化のレベルは、より具体的なエンティティからより一般的なエンティティまでのスケールに類似しています。


プロトタイプのオブジェクト指向プログラミング

プロトタイプのオブジェクト指向プログラミング言語は、従来のオブジェクト指向プログラミング言語よりもはるかに単純です。これは、プロトタイプのオブジェクト指向プログラミングでは、1つのタイプの抽象化(つまり、オブジェクト)しかないためです。たとえば、次のことを考慮してください。

+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity |                Comments               |
+----------------------+----------------+---------------------------------------+
| 0                    | John Doe       | Real World Entity.                    |
| 1                    | johnDoe        | Variable holding object.              |
| 2                    | man            | Prototype of object johnDoe.          |
| 3                    | human          | Prototype of object man.              |
+----------------------+----------------+---------------------------------------+

プロトタイプのオブジェクト指向プログラミング言語でわかるように、オブジェクトは実世界のエンティティ(この場合は単にオブジェクトと呼ばれる)または他のオブジェクト(この場合は抽象化されるオブジェクトのプロトタイプと呼ばれる)の抽象化です。したがって、プロトタイプは一般化されたものです。

プロトタイプのオブジェクト指向プログラミング言語のオブジェクトは、ex-nihilo(つまり、何もない状態)または別のオブジェクト(新しく作成されたオブジェクトのプロトタイプになる)から作成できます。

var human = {};
var man = Object.create(human);
var johnDoe = Object.create(man);

私の控えめな意見では、プロトタイプのオブジェクト指向プログラミング言語は、古典的なオブジェクト指向プログラミング言語よりも強力です。

  1. 抽象化のタイプは1つだけです。
  2. 汎化は単なるオブジェクトです。

ここまでで、古典的継承とプロトタイプ継承の違いを理解しているはずです。古典的な継承は、他のクラスから継承するクラスに限定されます。ただし、プロトタイプの継承には、他のプロトタイプから継承するプロトタイプだけでなく、プロトタイプから継承するオブジェクトも含まれます。


プロトタイプクラスの同型

プロトタイプとクラスは非常に似ていることに気づいたはずです。それは本当だ。彼らです。実際、それらは非常に似ているので、実際にプロトタイプを使用してクラスをモデル化できます。

function CLASS(base, body) {
    if (arguments.length < 2) body = base, base = Object.prototype;
    var prototype = Object.create(base, {new: {value: create}});
    return body.call(prototype, base), prototype;

    function create() {
        var self = Object.create(prototype);
        return prototype.hasOwnProperty("constructor") &&
            prototype.constructor.apply(self, arguments), self;
    }
}

上記のCLASS関数を使用して、クラスのようなプロトタイプを作成できます。

var Human = CLASS(function () {
    var milliseconds = 1
      , seconds      = 1000 * milliseconds
      , minutes      = 60 * seconds
      , hours        = 60 * minutes
      , days         = 24 * hours
      , years        = 365.2425 * days;

    this.constructor = function (name, sex, dob) {
        this.name = name;
        this.sex = sex;
        this.dob = dob;
    };

    this.age = function () {
        return Math.floor((new Date - this.dob) / years);
    };
});

var Man = CLASS(Human, function (Human) {
    this.constructor = function (name, dob) {
        Human.constructor.call(this, name, "male", dob);
        if (this.age() < 18) throw new Error(name + " is a boy, not a man!");
    };
});

var johnDoe = Man.new("John Doe", new Date(1970, 0, 1));

ただし、その逆は当てはまりません(つまり、クラスを使用してプロトタイプをモデル化することはできません)。これは、プロトタイプはオブジェクトですが、クラスはオブジェクトではないためです。それらは完全に異なるタイプの抽象化です。


結論

要約すると、抽象化は「特定の例から共通の特徴を抽出することによって形成される一般的な概念」であり、一般化は「より具体的な抽象化の抽象化」であることがわかりました。また、プロトタイプ継承とクラシック継承の違い、および両方が同じコインの2つの面である方法についても学びました。

パーティングノートで、プロトタイプの継承には、プロトタイプのパターンとコンストラクターのパターンという2つのパターンがあることに注意したいと思います。原型パターンは原型継承の標準パターンであり、コンストラクタパターンは原型継承を古典的な継承のようにするために使用されます。個人的には私はプロトタイプのパターンを好みます。

PS私は、ブログ投稿「プロトタイプの継承が重要である理由」を書いて、「クラシックに対するプロトタイプの継承の利点?」という質問に答えた人です。私の答えは受け入れられた答えです。


2
あなたの素晴らしい答えをありがとう。プロトタイプパターンがコンストラクターパターンと比較してどのように優れているかを理解する必要があります。
SivaRajini 2013年

1
私はブログでコンストラクターとプロトタイプについての比較批評を書きました:aaditmshah.github.io/why-prototypal-inheritance-matters/…–
Aadit M Shah

それで、継承を実現するためにJavaScriptで関数を使用するとき、古典的な継承モデルをいくらか使用し、プレーンオブジェクトを使用するとき、それはプロトタイプ継承(両方とも内部的にプロトタイプ継承に従う)と言うのは正しいでしょうか?
スワニディ

1
@Swanidhiいいえ。JavaScriptを使用している場合は、プロトタイプ継承モデルを使用しています。ただし、JavaScriptには2つのフレーバーのプロトタイプ継承があります。関数の使用(つまり、コンストラクターパターン)とオブジェクトの使用(つまり、プロトタイプパターン)です。
Aadit M Shah 2015

5
@Swanidhiいいえ。それはまだプロトタイプです。JavaScriptには「クラス」がないため、コンストラクターを含め、クラシックのJavaScriptにはまったく何もありません。それはまだプロトタイプの継承です。人々が古典的な継承と混同する奇妙な形のプロトタイプ継承。簡単に説明すると、programming with classes = classical inheritanceprogramming with prototypes = prototypal inheritanceprogramming with constructors = weird form of prototypal inheritance that looks a lot like classical inheritance。物事が明確になることを願っています。
Aadit M Shah 2015

8

継承に入る前に、JavaScriptでインスタンス(オブジェクト)を作成する2つの主要なモデルを見てみましょう。

クラシックモデル:オブジェクトはブループリント(クラス)から作成されます

class Person {
  fn() {...}
} // or constructor function say, function Person() {}

// create instance
let person = new Person();

プロトタイプモデル:オブジェクトは別のオブジェクトから直接作成されます。

// base object
let Person = { fn(){...} }

// instance
let person = Object.create(Person);

どちらの場合でも、継承*は、プロトタイプオブジェクトを使用してオブジェクトをリンクすることによって実現されます。

(*基本クラスのメソッドは、プロトタイプオブジェクトを通じて派生クラスを介してアクセスでき、派生クラスに明示的に存在する必要はありません。)

これはよりよく理解するための良い説明です(http://www.objectplayground.com/


0

犬は動物です。スザンナは犬です。古典的な継承において、Animalクラスである、DogのサブクラスでありAnimal、そしてsuzannaAのインスタンスですDog

プロトタイプの継承では、クラスはありません。あなたは持っているanimalオブジェクトです。A dogは別のオブジェクトで、複製および拡張されますanimal(プロトタイプオブジェクト)。suzannaをコピーおよび拡張する3番目のオブジェクトdogです。

let animal = {hasChlorophyl: false};

let dog = Object.create(animal);
Object.assign(dog, {
  speak() {
    console.log("Woof!");
  }
});

let suzanna = Object.create(dog);
Object.assign(suzanna, {
  name: "Suzanna"
});

suzanna.speak();

Dog代わりに書いた場合dog、特にDogある種の「コンストラクター」関数を作成した場合は、プロトタイプの継承を行っていません。あなたは(疑似)古典的な継承を行っていますObject.create()これを達成するために使用しているという事実は、プロトタイプの継承を行っていることを意味するものではありません。

実際、JavaScriptはプロトタイプの継承のみをサポートしています。わかりにくいnew演算子と.prototype属性は、プロトタイプの継承を(疑似)古典的な継承のようにするためにあります。

Douglas Crockfordは、彼の著書「JavaScript:The Good Parts」でこれについて詳しく検討しています。

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