#javascript #inheritance #prototype #constructor
#javascript #наследование #прототип #constructor
Вопрос:
Я пытаюсь заставить прототипное наследование работать следующим образом:
// Parent constructor
function Parent(obj) {
this.id = obj.id || 0;
this.name = obj.name || "";
};
// Child constructor
function Child(obj) {
Parent.call(this,obj);
this.type = obj.type || "";
}
Child.prototype = new Parent;
Кажется учебным пособием … но передача obj
как родителю, так и потоку, похоже, вызывает проблемы; Parent
говорит obj
, что не определено, когда потомок пытается создать прототип через Child.prototype = new Parent;
. Единственный способ, которым я могу обойти это, — это уродливый взлом:
// 'Hacked' Parent constructor
function Parent(obj) {
if (obj) {
this.id = obj.id || 0;
this.name = obj.name || "";
}
};
Конечно, есть способ получше, но я нигде не могу найти ответа. Пожалуйста, помогите!!
Ответ №1:
Child.prototype = new Parent;
создает нового родительского элемента без каких-либо параметров и возвращает его прототип, присваивая его Child.prototype
. Этот подход работает хорошо, но проблема в том, что он работает только на родительских элементах с параметрами 0 ( new Parent
является эквивалентом new Parent()
в данном контексте).
Для родительских конструкторов с параметрами я предлагаю определить функцию inherits для обработки вашего наследования.
Исправление, которое я предложил ранее, было исправлено Child.prototype = Parent.prototype;
. Это сработает, но теперь Child.prototype
это ссылка на Parent.prototype
, а не на объект. Другими словами, если вы добавляете метод hello дочернему элементу, то родительский элемент также получает этот метод. Плохие времена!
Лучшее решение этой проблемы — определить некоторую inherits
функцию как таковую:
function inherit(parentPrototype) {
function F() {};
F.prototype = parentPrototype;
return new F;
}
То, что мы здесь делаем, немного отличается от простого присвоения Parent.prototype
дочернему элементу. Мы создаем новую функцию, которая имеет родительский прототип и возвращает новый экземпляр функции F. Итак, когда вы добавляете методы дочернему элементу, вы фактически добавляете их к прототипу F
функции.
Чтобы создать дочерний объект сейчас, вы бы сделали:
function Child() {};
Child.prototype = inherit(Parent.prototype); // returns an instance of F
Затем вы можете добавлять методы к дочернему элементу, не затрагивая родительский.
Комментарии:
1. Отличный ответ — спасибо 🙂 Одно уточнение (относящееся к вашему примеру метода hello()) … в моем реальном случае у Parent есть методы, которые я, очевидно, хочу, чтобы Child унаследовал. Есть ли что-то особенное, что мне нужно сделать, чтобы включить это, поскольку
new
это больше не используется? (например, вручную назначитьthis
, вручную создать прототип методов дочернему элементу и т.д.) Еще раз спасибо!2.
Child.prototype = Parent;
просто добавит функцию в качестве объекта-прототипа, прототип которого не будет проверяться. Однако, если вы добавляете методы непосредственно в саму функцию родительского конструктора, дочерний объект может получить к ним доступ, правда, но сам родительский элемент не работает, так что на самом деле это не наследование, как я его вижу.3. Нет,
Parent
это ссылка на функцию, прототип которой имеет все объявленныеParent
методы. Когда вы устанавливаете дляChild
прототипа значениеParent
, он получает все эти методы. Затем вы добавляете кChild
прототипу свои дочерние методы.4. Проблема, на которую я ссылался, заключается в настройке любых дочерних методов до того, как вы назначили его прототип для
Parent
.Child.prototype = Parent
аналогично любому другому присваиванию; вы теряете значение, ранее сохраненное вChild.prototype
.5. В моем первоначальном ответе на самом деле была небольшая ошибка, пожалуйста, перечитайте сообщение сейчас, поскольку я его исправил.
Ответ №2:
Это выглядит немного приятнее:
function Parent(obj) {
if (!obj) {return;}
this.id = obj.id || 0;
this.name = obj.name || "";
}
Ваш стиль наследования больше соответствует «классическому» шаблону.
Вы также можете сделать это:
Child.prototype = Parent.prototype;
…но любые последующие дополнения к Child.prototype также окажутся в родительском прототипе (и наоборот).
Возможно, именно поэтому такое поведение часто оборачивается для возврата объекта напрямую, когда это необходимо, таким образом, что вы не можете так же легко перезаписать родительский элемент (хотя вы можете через obj.constructor.prototype):
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
Комментарии:
1. Спасибо за ответ. Я видел это решение на сайте Crockford, но не до конца понял его; Теперь я ясно вижу преимущества!
2. И еще одно: если вы не хотите возиться с проверками типов или служебными методами, вы могли бы просто передать пустой объект:
Child.prototype = new Parent({});
Кроме того, вы можете изменить или создать новую версию функции, предоставленной мной и Эммерихом, чтобы обеспечить более простой синтаксис:Child.prototype = inherit(Parent);
также.3. Спасибо — я использовал метод inherit / create, который вы оба предложили, и он работает хорошо. Мне нужны служебные методы, чтобы пустой объект не работал, но все равно приятная простая идея.