классическое наследование против прототипного наследования в javascript


118

Я поискал в Google так много ссылок и не могу понять разницу между классическим и прототипным наследованием?

Я кое-чему научился из них, но все еще не понимаю концепции.

Классическое наследование

// 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
не уверен, о чем вы здесь говорите - первый блок кода - это прототипное наследование, а не классическое. Ваш второй блок кода вообще не наследуется!
Альнитак

Это может быть объяснено: blog.stephenwyattbush.com/2012/05/01/…
HasanAboShally

@alnitak developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… эта ссылка говорит о том, что одно было классическим наследованием. вот почему я запутался.
SivaRajini

Подробнее о том, почему вы, возможно, захотите избежать классического наследования, см. Мой доклад «Классическое наследование устарело: как мыслить в прототипном объектно- ориентированном подходе
Эрик Эллиотт

Ответы:


248

Оба примера кода, которые вы продемонстрировали в своем вопросе, используют прототипное наследование. Фактически любой объектно-ориентированный код, который вы пишете на JavaScript, является парадигмой прототипного наследования. В JavaScript просто нет классического наследования. Это должно немного прояснить ситуацию:

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

Как видите, прототипное и классическое наследование - это две разные парадигмы наследования. Некоторые языки, такие как Self, Lua и JavaScript, поддерживают прототипное наследование. Однако большинство языков, таких как C ++, Java и C #, поддерживают классическое наследование.


Краткий обзор объектно-ориентированного программирования

И прототипное, и классическое наследование - это парадигмы объектно-ориентированного программирования (т.е. они имеют дело с объектами). Объекты - это просто абстракции, которые инкапсулируют свойства сущности реального мира (т.е. они представляют реальные словесные вещи в программе). Это называется абстракцией.

Абстракция: представление вещей реального мира в компьютерных программах.

Теоретически абстракция определяется как «общее понятие, образованное путем извлечения общих черт из конкретных примеров». Однако для объяснения мы будем использовать вместо этого вышеупомянутое определение.

Теперь у некоторых объектов много общего. Например, грязевой байк и Harley Davidson имеют много общего.

Грязевой байк:

Грязевой байк.

Харлей Дэвидсон:

Харлей Дэвидсон

Грязевой байк и Харлей Дэвидсон - оба мотоцикла. Следовательно, байк - это обобщение как грязевого велосипеда, так и Harley Davidson.

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

В приведенном выше примере байк, грязевой байк и Harley Davidson - все абстракции. Однако байк - это более общая абстракция от грязевого велосипеда и Harley Davidson (то есть и грязевой байк, и Harley Davidson являются конкретными типами велосипедов).

Обобщение: абстракция более конкретной абстракции.

В объектно-ориентированном программировании мы создаем объекты (которые являются абстракциями сущностей реального мира) и используем классы или прототипы для создания обобщений этих объектов. Обобщения создаются посредством наследования. Велосипед - это обобщение грязевого велосипеда. Следовательно, грязевые велосипеды унаследованы от мотоциклов.


Классическое объектно-ориентированное программирование

В классическом объектно-ориентированном программировании есть два типа абстракций: классы и объекты. Как упоминалось ранее, объект - это абстракция сущности реального мира. С другой стороны, класс - это абстракция объекта или другого класса (т. Е. Это обобщение). Например, рассмотрим:

+----------------------+----------------+---------------------------------------+
| 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();

Подводя итог, в классических объектно-ориентированных языках программирования объекты - это абстракции сущностей реального мира, а классы - это обобщения (т. Е. Абстракции объектов или других классов).

Следовательно, по мере увеличения уровня абстракции сущности становятся более общими, а по мере уменьшения уровня абстракции сущности становятся более конкретными. В этом смысле уровень абстракции аналогичен шкале от более конкретных сущностей до более общих.


Прототипное объектно-ориентированное программирование

Прототипные объектно-ориентированные языки программирования намного проще классических объектно-ориентированных языков программирования, потому что в прототипном объектно-ориентированном программировании у нас есть только один тип абстракции (т. Е. Объекты). Например, рассмотрим:

+----------------------+----------------+---------------------------------------+
| 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. Есть только один тип абстракции.
  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));

Однако обратное неверно (т.е. вы не можете использовать классы для моделирования прототипов). Это потому, что прототипы являются объектами, а классы - не объектами. Это совершенно другой тип абстракции.


Вывод

Подводя итог, мы узнали, что абстракция - это «общее понятие, образованное путем извлечения общих черт из конкретных примеров», а обобщение - это «абстракция более конкретной абстракции» . Мы также узнали о различиях между прототипным и классическим наследованием и о том, что оба они являются двумя сторонами одной медали.

На прощание я хотел бы отметить, что существует два шаблона прототипного наследования: шаблон прототипа и шаблон конструктора. Шаблон прототипа - это канонический шаблон наследования прототипа, тогда как шаблон конструктора используется для того, чтобы сделать прототипное наследование более похожим на классическое наследование. Лично я предпочитаю прототипный паттерн.

PS Я тот парень, который написал сообщение в блоге « Почему имеет значение прототипное наследование » и ответил на вопрос « Преимущества прототипного наследования перед классическим? ». Мой ответ - принятый ответ.


2
спасибо за прекрасный ответ. Мне нужно понять, чем прототипный шаблон лучше сравнивать с шаблоном конструктора. любой пример?
SivaRajini

1
Я написал сравнительную критику конструкторов и прототипов в своем блоге: aaditmshah.github.io/why-prototypal-inheritance-matters/…
Aadit M Shah

Итак, будет ли правильно сказать, что, когда мы используем функции в javascript для достижения наследования, мы в некоторой степени используем классическую модель наследования, а когда мы используем простые объекты, это прототипное наследование (как внутренне, следуя прототипному наследованию)?
Swanidhi

1
@Swanidhi Нет. Если вы используете JavaScript, значит, вы используете прототипную модель наследования. Однако в JavaScript есть две разновидности прототипного наследования: использование функций (т. Е. Шаблон конструктора) и использование объектов (т. Е. Шаблон-прототип).
Aadit M Shah

5
@Swanidhi Нет. Это все еще прототип. В JavaScript нет «классов» и, следовательно, в классическом JavaScript нет абсолютно ничего, включая конструкторы. Это все еще прототипное наследование. Просто странная форма прототипного наследования, которую люди путают с классическим наследованием. Короче говоря, programming with classes = classical inheritance, programming with prototypes = prototypal inheritance, programming with constructors = weird form of prototypal inheritance that looks a lot like classical inheritance. Надеюсь, что это проясняет ситуацию.
Aadit M Shah

8

Прежде чем перейти к наследованию, мы рассмотрим два основных модели для создания экземпляров (объектов) в javascript:

Классическая модель: объект создается из чертежа (класса)

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и suzannaявляется экземпляром Dog.

В прототипном наследовании нет класса. У вас animalесть объект. A dog- это еще один объект, который клонирует и расширяет animal(объект-прототип). suzannaэто третий объект, который копирует и расширяет 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атрибут используются для того, чтобы прототипное наследование выглядело как (псевдо) классическое наследование.

Дуглас Крокфорд подробно исследует это в своей книге «JavaScript: хорошие стороны».

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.