Понимание прототипного наследования в JavaScript

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

первый блок:

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 класс и as this.Fly на SuperCar класса?

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

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

var myCar = new Car("Car");

6 ответов


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

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

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


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

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

var mySuperCar = new SuperCar("SuperCar");

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

  1. свежий, пустой объект создать экземпляр.
  2. внутренний прототип нового объекта установлен на автомобиль.
  3. запускается функция конструктора суперкара.
  4. готовый объект возвращается и устанавливается в mySuperCar.

обратите внимание, как JavaScript не вызывал автомобиль для вас. Прототипы, как они есть, любое свойство или метод, который вы не устанавливаете для суперкара, будут искать в машине. Иногда это хорошо, например, у суперкара нет метода привода, но он может делиться Автомобиль один, поэтому все суперкары будут использовать один и тот же метод привода. В других случаях вы не хотите делиться, как каждый суперкар, имеющий свое собственное имя. Итак, как можно установить имя каждого суперкара на его собственную вещь? Вы могли бы установить это.Имя внутри функции конструктора суперкара:

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

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

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

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

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

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

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

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

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


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

  1. вы вызовете конструктор автомобиля дважды, один раз из суперкара.prototype = new Car() линия и другая из "конструктора кражи" линии автомобиля.звоните (это...вы можете создать вспомогательный метод для наследования прототипов, и вашему конструктору автомобиля придется запускать только один раз, делая установку более эффективный.
  2. Суперкара.прототип.constructor = линия суперкара позволит вам использовать instanceof для идентификации конструктора. Некоторые люди хотят, чтобы это другие просто избежать использования instanceof
  3. Reference vars like: var arr = ['one','two'] при определении на супер (например, автомобиль) будет совместно использоваться всеми экземплярами. Это означает, inst1 в.прибытие.push ['three'], inst2.прибытие.push ['four'], etc., будет отображаться для всех случаев! По сути, статическое поведение, которое вы, вероятно, не хотеть.
  4. второй блок определяет метод fly в конструкторе. Это означает, что каждый раз, когда он вызывается, будет создан" объект метода". Лучше использовать прототип для методов! Однако вы можете сохранить его в конструкторе, если хотите - вам просто нужно защитить, поэтому вы фактически инициализируете литерал прототипа только один раз (псевдо): if (SuperCar.прототип.метод mymethod != 'функция')...затем определите свой прототип литерала.
  5. ' зачем вызывать машину.вызов(это имя)....'Я не у вас есть время внимательно посмотреть на ваш код, поэтому я могу ошибаться, но обычно это так, что каждый экземпляр может сохранить свое собственное состояние, чтобы исправить проблему "статического" поведения цепочки прототипов, которую я описал выше.

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


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


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


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

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

первый создает только один 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? Is constructor свойство переопределяется, когда prototype установлен? Я закомментировал эту строку и ничего не изменилось.

на constructor свойство не переопределено, когда prototype установлен. Но конструктор new Car() is Car, так что если вы установили new Car() to SuperCar.prototype, то, очевидно,SuperCar.prototype.constructor is Car.

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

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

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

var myCar = new Car("Car");

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

не было бы полезно, чтобы позвонить new Car(name) внутри SuperCar конструктор, Как создать другое объект, в то время как вам действительно нужно расширить