Понимание прототипного наследования в JavaScript
Я новичок в JavaScript OOP. Не могли бы вы объяснить разницу между следующими блоками кода. Я тестировал, и оба блока работают. Какая наилучшая практика и почему?
Первый блок:
function Car(name){
this.Name = name;
}
Car.prototype.Drive = function(){
document.write("My name is " + this.Name + " and I'm driving. <br />");
}
SuperCar.prototype = new Car();
SuperCar.prototype.constructor = SuperCar;
function SuperCar(name){
Car.call(this, name);
}
SuperCar.prototype.Fly = function(){
document.write("My name is " + this.Name + " and I'm flying! <br />");
}
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(){
document.write("My name is " + this.Name + " and I'm driving. <br />");
}
}
SuperCar.prototype = new Car();
function SuperCar(name){
Car.call(this, name);
this.Fly = function(){
document.write("My name is " + this.Name + " and I'm flying! <br />");
}
}
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
? Является ли свойство конструктора переопределенным при установке prototype
? Я прокомментировал эту строку, и ничего не изменилось.
Зачем вызывать Car.call(this, name);
в конструкторе SuperCar
? Не будут ли свойства и методы Car
"унаследованы", когда я сделаю
var myCar = new Car("Car");
Ответы
Ответ 1
Два блока отличаются таким образом, что в первом примере Drive()
будет существовать только один раз, когда во втором подходе Drive()
будет существовать на один экземпляр (каждый раз, когда вы выполняете new Car()
, будет создана функция Drive()
еще раз). Или другой сказал, что первый использует прототип для хранения функции, а второй - для конструктора. Поиск функций - это конструктор, а затем прототип. Поэтому для поиска Drive()
он находит это независимо от того, находится ли он в конструкторе или в прототипе. Использование прототипа более эффективно, потому что обычно вам нужна функция только один раз для каждого типа.
Вызов new
в javascript автоматически устанавливает конструктор в прототипе. Если вы переписываете прототип, поэтому вам нужно установить конструктор вручную.
Наследование в javascript не имеет ничего похожего на super
. Поэтому, если у вас есть подкласс, единственный шанс вызвать супер-конструктор - по его имени.
Ответ 2
Чтобы добавить к ответ Норберта Хартла, SuperCar.prototype.constructor не нужен, но некоторые люди используют его как удобный способ получить конструктивную функцию объект (объекты SuperCar в этом случае).
Как раз из первого примера, Car.call(это, имя) находится в функции конструктора SuperCar, потому что когда вы это делаете:
var mySuperCar = new SuperCar("SuperCar");
Это то, что делает JavaScript:
- Создается новый пустой объект.
- Внутренний прототип нового объекта установлен в "Автомобиль".
- Выполняется функция конструктора SuperCar.
- Готовый объект возвращается и устанавливается в mySuperCar.
Обратите внимание, что JavaScript не вызывал Car для вас. Прототипы, являющиеся такими, какие они есть, любое свойство или метод, которые вы не настроили для SuperCar, будут рассмотрены в Car. Иногда это хорошо, например. SuperCar не имеет метода Drive, но он может совместно использовать Car one, поэтому все SuperCars будут использовать тот же метод 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 to экземпляр SuperCar, который мы собираем. Presto! Теперь каждый SuperCar получает свое собственное свойство Name.
Чтобы завернуть, Car.call(this, name)
в конструкторе SuperCar дает каждому новому объекту SuperCar свое уникальное свойство Name, но без дублирования кода, который уже находится в Car.
Прототипы не страшны, как только вы их понимаете, но они совсем не похожа на классическую модель ООП класса/наследования. Я написал статью о концепции прототипов в JavaScript. Он написан для игрового движка, который использует JavaScript, но он тот же движок JavaScript, который используется Firefox, поэтому все должно быть релевантным. Надеюсь, это поможет.
Ответ 3
Норберт, вы должны заметить, что ваш первый пример - это в значительной степени то, что Дуглас Крокфорд называет псевдоклассическим наследованием. Что-то об этом нужно отметить:
- Дважды вы вызовите конструктор Car, один раз из строки SuperCar.prototype = new Car(), а другой - из строки "Конструктор кражи" Car.call(этот... вы можете создать вспомогательный метод для наследования прототипов вместо этого, и ваш конструктор Car должен будет запускать только один раз, делая установку более эффективной.
- Строка SuperCar.prototype.constructor = SuperCar позволит вам использовать instanceof для идентификации конструктора. Некоторые люди хотят, чтобы другие просто избегали использования экземпляра
- Ссылки, такие как: var arr = ['one', 'two'], если они определены в супер (например, автомобиль), будут разделяться всеми экземплярами. Это означает, что inst1.arr.push ['three'], inst2.arr.push ['four'] и т.д. Будут отображаться для всех экземпляров! По существу, статическое поведение, которое вы, вероятно, не хотите.
- Второй блок определяет метод fly в конструкторе. Это означает, что каждый раз, когда он вызывается, создается "объект метода". Лучше использовать прототип для методов! Вы можете, однако, сохранить его в конструкторе, если хотите - вам просто нужно защититься, чтобы вы только на самом деле инициализировали прототип литерала один раз (псевдо): if (SuperCar.prototype.myMethod!= 'Function')... затем определите ваш прототип литерала.
- 'Зачем вызывать Car.call(это, имя)....': У меня нет времени внимательно посмотреть на ваш код, чтобы я мог ошибаться, но обычно это так, что каждый экземпляр может сохранять собственное состояние для исправления проблемы "статического" поведения в цепочке прототипов, описанной выше.
Наконец, я хотел бы упомянуть, что у меня есть несколько примеров кода TDD JavaScript Inheritance, который работает здесь: TDD JavaScript Inheritance Code and Essay Я хотел бы получить ваши отзывы, поскольку я надеюсь улучшить его и сохранить его открытым исходным кодом. Цель состоит в том, чтобы помочь классическим программистам быстро ускорить работу с JavaScript, а также дополнить исследование как книгами Crockford, так и Zakas.
Ответ 4
Я не уверен на 100%, но я считаю, что разница в том, что второй пример просто дублирует содержимое класса Car в объект SuperCar, а первый связывает прототип SuperCar с классом Car, так что время выполнения изменения в классе Car также влияют на класс SuperCar.
Ответ 5
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