Понимание наследования прототипов в JavaScript - PullRequest
170 голосов
/ 21 мая 2009

Я новичок в ООП JavaScript. Можете ли вы объяснить разницу между следующими блоками кода? Я проверил и оба блока работают. Какая лучшая практика и почему?

Первый блок:

function Car(name){
    this.Name = name;
}

Car.prototype.Drive = function(){
    console.log("My name is " + this.Name + " and I'm driving.");
}

SuperCar.prototype = new Car();
SuperCar.prototype.constructor = SuperCar;

function SuperCar(name){
    Car.call(this, name);
}

SuperCar.prototype.Fly = function(){
    console.log("My name is " + this.Name + " and I'm flying!");
}

var myCar = new Car("Car");
myCar.Drive();

var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();

Второй блок:

function Car(name){
    this.Name = name;
    this.Drive = function(){ 
        console.log("My name is " + this.Name + " and I'm driving.");
    }
}

SuperCar.prototype = new Car();

function SuperCar(name){
    Car.call(this, name);
    this.Fly = function(){
        console.log("My name is " + this.Name + " and I'm flying!");
    }
}

var myCar = new Car("Car");
myCar.Drive();

var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();

Почему автор добавил методы Drive и Fly, используя prototype, и не объявил их как метод this.Drive внутри класса Car и как this.Fly в классе SuperCar

Почему SuperCar.prototype.constructor необходимо установить обратно на SuperCar? Переопределено ли свойство constructor, если задано prototype? Я закомментировал эту строку и ничего не изменилось.

Зачем вызывать Car.call(this, name); в конструкторе SuperCar? Свойства и методы Car не будут «унаследованы», когда я сделаю

var myCar = new Car("Car");

Ответы [ 6 ]

140 голосов
/ 21 мая 2009

Чтобы добавить к ответу Норберта Хартла , SuperCar.prototype.constructor не нужен, но некоторые люди используют его как удобный способ получения функции построения объекта (в данном случае это объекты SuperCar) .

Только в первом примере Car.call (this, name) находится в функции конструктора SuperCar, потому что, когда вы делаете это:

var mySuperCar = new SuperCar("SuperCar");

Вот что делает JavaScript:

  1. Создается новый пустой объект.
  2. Для внутреннего прототипа нового объекта установлено значение Car.
  3. Функция конструктора SuperCar запускается.
  4. Готовый объект возвращается и устанавливается в mySuperCar.

Обратите внимание, что JavaScript не назвал Car для вас. Прототипы, какими бы они ни были, любые свойства или методы, которые вы не установили для SuperCar, будут найдены в Car. Иногда это хорошо, например У SuperCar нет метода Drive, но он может использовать метод Car, поэтому все SuperCar будут использовать один и тот же метод Drive. В других случаях вы не хотите делиться, например, каждый SuperCar имеет свое имя. Так как же настроить имя каждого SuperCar на свое? Вы можете установить this.Name внутри функции конструктора SuperCar:

function SuperCar(name){
    this.Name = name;
}

Это работает, но подождите секунду. Разве мы не сделали то же самое в конструкторе Car? Не хочу повторяться. Так как Car уже задает имя, давайте просто назовем его.

function SuperCar(name){
    this = Car(name);
}

Ой, вы никогда не захотите изменить специальную ссылку на объект this. Помните 4 шага? Держитесь за тот объект, который дал вам JavaScript, потому что это единственный способ сохранить драгоценную внутреннюю связь прототипа между вашим объектом SuperCar и Car. Так как же нам установить Name, не повторяя себя и не выбрасывая наш новый объект SuperCar, JavaScript потратил столько особых усилий, чтобы подготовиться к нам?

Две вещи. Один: значение this является гибким. Два: Автомобиль - это функция. Можно вызывать Car не с нетронутым, свежим экземпляром объекта, а вместо этого, скажем, с объектом SuperCar. Это дает нам окончательное решение, которое является частью первого примера в вашем вопросе:

function SuperCar(name){
    Car.call(this, name);
}

Как функция, Car может быть вызван с помощью метода вызова функции , который изменяет значение this в Car на экземпляр SuperCar, который мы создаем. Presto! Теперь каждый SuperCar получает свое собственное свойство Name.

В заключение, Car.call(this, name) в конструкторе SuperCar дает каждому новому объекту SuperCar свое уникальное свойство Name, но без дублирования кода, который уже находится в Car.

Прототипы не страшны, когда вы их понимаете, но они совсем не похожи на классическую модель ООП класса / наследования. Я написал статью о концепции прототипов в JavaScript . Он написан для игрового движка, использующего JavaScript, но это тот же движок JavaScript, что и Firefox, поэтому он должен быть актуальным. Надеюсь, это поможет.

82 голосов
/ 21 мая 2009

Два блока отличаются тем, что в первом примере Drive() будет существовать только один раз, тогда как при втором подходе Drive() будет существовать для каждого экземпляра (Каждый раз, когда вы делаете new Car(), будет создана функция drive() снова). Или иначе сказал, что первый использует прототип для хранения функции, а второй конструктор. Поиск функций - конструктор, а затем прототип. Так что для вашего поиска Drive() он находит его независимо от того, находится он в конструкторе или в прототипе. Использование прототипа более эффективно, потому что обычно вам нужна функция только один раз для каждого типа.

Вызов new в javascript автоматически устанавливает конструктор в прототипе. Если вы перезаписываете прототип, вы должны установить конструктор вручную.

Наследование в javascript не имеет ничего общего с super. Поэтому, если у вас есть подкласс, единственный шанс вызвать супер-конструктор - это его имя.

8 голосов
/ 29 октября 2009

Норберт, вы должны заметить, что ваш первый пример в значительной степени то, что Дуглас Крокфорд называет псевдоклассическим наследованием. Что следует отметить по этому поводу:

  1. Вы будете вызывать конструктор Car дважды, один раз из строки SuperCar.prototype = new Car (), а другой из строки «украсть конструктор» Car.call (это ... вы можете создать вспомогательный метод для наследования прототипов вместо этого ваш конструктор Car будет работать только один раз, что сделает настройку более эффективной.
  2. Строка SuperCar.prototype.constructor = SuperCar позволит вам использовать instanceof для идентификации конструктора. Некоторые люди хотят этого, другие просто избегают использования instanceof
  3. Ссылочные переменные, такие как: var arr = ['one', 'two'], если они определены в супер (например, Car), будут разделены ВСЕМИ экземплярами. Это означает, что inst1.arr.push ['three'], inst2.arr.push ['four'] и т. Д. Будут отображаться для всех экземпляров! По сути, статичное поведение, которое вам, вероятно, не нужно.
  4. Ваш второй блок определяет метод fly в конструкторе. Это означает, что при каждом вызове будет создаваться «объект метода». Лучше использовать прототип для методов! Однако вы МОЖЕТЕ оставить его в конструкторе, если хотите, - вам просто нужно принять меры, чтобы вы фактически инициализировали литерал прототипа только один раз (псевдо): if (SuperCar.prototype.myMethod! = 'Function') ... затем определите ваш прототип литерала.
  5. «Зачем вызывать Car.call (это, имя) ....»: у меня нет времени внимательно посмотреть на ваш код, поэтому я могу ошибаться, но обычно это так, чтобы каждый экземпляр мог сохранять свое собственное состояние исправить проблему «статичного» поведения цепочки прототипов, которую я описал выше.

Наконец, я хотел бы отметить, что у меня есть несколько примеров кода наследования JavaScript TDD, который работает здесь: Код наследования JavaScript TDD и эссе Я хотел бы получить ваши отзывы, как я надеюсь улучшить его и оставить его с открытым исходным кодом. Цель состоит в том, чтобы помочь классическим программистам быстро освоить JavaScript, а также дополнить изучение книг Крокфорда и Закаса.

1 голос
/ 07 октября 2015
function abc() {
}

Методы и свойства прототипа, созданные для функции abc

abc.prototype.testProperty = 'Hi, I am prototype property';
abc.prototype.testMethod = function() { 
   alert('Hi i am prototype method')
}

Создание новых экземпляров для функции abc

var objx = new abc();

console.log(objx.testProperty); // will display Hi, I am prototype property
objx.testMethod();// alert Hi i am prototype method

var objy = new abc();

console.log(objy.testProperty); //will display Hi, I am prototype property
objy.testProperty = Hi, I am over-ridden prototype property

console.log(objy.testProperty); //will display Hi, I am over-ridden prototype property

http://astutejs.blogspot.in/2015/10/javascript-prototype-is-easy.html

1 голос
/ 21 мая 2009

Я не уверен на 100%, но я считаю, что разница в том, что второй пример просто дублирует содержимое класса Car в объект SuperCar, а первый связывает прототип SuperCar с классом Car, так что во время выполнения изменения в классе автомобилей также влияют на класс SuperCar.

0 голосов
/ 01 сентября 2018

Здесь есть несколько вопросов:

Не могли бы вы объяснить разницу между следующими блоками кода. Я проверил и оба блока работают.

Первая создает только одну функцию Drive, вторая создает две из них: одну на myCar и другую на mySuperCar.

Вот код, который будет давать разные результаты при выполнении первого или второго блока:

myCar.Fly === mySuperCar.Fly // true only in the first case
Object.keys(myCar).includes("Fly") // true only in the second case
Object.keys(Car.prototype).length === 0 // true only in the second case

Какая лучшая практика и почему?
Почему автор добавил методы Drive и Fly, используя prototype, но не объявляет их как this.Drive метод внутри Car класса и this.Fly в SuperCar классе?

Лучше определять методы по прототипу, потому что:

  • каждый метод определяется только один раз
  • каждый метод также доступен для экземпляров, которые были созданы без выполнения конструктора (что имеет место при вызове Object.create(Car.prototype));
  • вы можете проверить, на каком уровне в цепочке прототипов экземпляра определен определенный метод.

Почему SuperCar.prototype.constructor необходимо установить обратно на SuperCar? Переопределено ли свойство constructor, если установлено prototype? Я закомментировал эту строку, и ничего не изменилось.

Свойство constructor не переопределяется, если установлено prototype. Но конструктор new Car() равен Car, поэтому, если вы установите new Car() на SuperCar.prototype, то, очевидно, SuperCar.prototype.constructor будет Car.

Пока вы не переназначены на prototype, существует неизменность: Constructor.prototype.constructor === Constructor. Например, это верно для Car: Car.prototype.constructor === Car, но в равной степени верно для Array, Object, String, ... и т. Д.

Но если вы переназначаете другой объект на prototype, эта инвариантность нарушается. Обычно это не проблема (как вы заметили), но лучше восстановить ее, поскольку она отвечает на вопрос «Какой конструктор использует этот объект-прототип при создании новых экземпляров?» Некоторый код может делать это осмотр и зависит от этого. См. «Почему необходимо установить конструктор прототипа?» для таких случаев.

Зачем вызывать Car.call(this, name); в SuperCar конструкторе? Не будут ли свойства и методы Car «унаследованы», когда я сделаю

var myCar = new Car("Car");

Если вы не выполните Car.call(this, name);, тогда ваш SuperCar экземпляр не будет иметь свойства name. Конечно, вы могли бы решить просто сделать this.name = name; вместо этого, который просто копирует код в конструкторе Car, но в более сложных ситуациях было бы плохой практикой иметь такое дублирование кода.

Не было бы полезно вызывать new Car(name) в конструкторе SuperCar, так как это создаст еще один объект, в то время как вам действительно нужно расширить объект this. Не используя new (используя вместо этого call), вы фактически указываете функции Car не запускаться как конструктор (то есть не создавать новый объект), а использовать передаваемый вами объект вместо этого.

Времена изменились

В современных версиях JavaScript вы можете использовать super(name) вместо Car.call(this, name):

function SuperCar(name) {
    super(name);
}

Сегодня вы также должны использовать синтаксис class и написать первый блок кода из вопроса следующим образом:

class Car {
    constructor(name) {
        this.name = name;
    }
    drive() {
        console.log(`My name is ${this.name} and I'm driving.`);
    }
}

class SuperCar extends Car {
    constructor(name) {
        super(name);
    }
    fly() {
        console.log(`My name is ${this.name} and I'm flying!`);
    }
}

const myCar = new Car("Car");
myCar.drive();

const mySuperCar = new SuperCar("SuperCar");
mySuperCar.drive();
mySuperCar.fly();

Обратите внимание, что вам даже не нужно упоминать свойство prototype для достижения цели. Синтаксис class ... extends также заботится о настройке свойства prototype.constructor, как это сделал первый блок в вашем вопросе.

...