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


я новичок в 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 164

6 ответов:

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

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

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

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

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

var mySuperCar = new SuperCar("SuperCar");

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

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

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

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) в конструкторе суперкара каждый новый объект суперкара имеет собственное уникальное свойство имени, но без дублирования кода, который уже находится в автомобиле.

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

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

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

The 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 конструктор? Не будут ли свойства и методы автомобиля "унаследованы", когда я делаю

var myCar = new Car("Car");

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

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