Правильный способ наследования
Я читал исходный код RxJS4 и наткнулся на функцию, которая выполняет наследование ( https://github.com/Reactive-Extensions/RxJS/blob/master/src/core/internal/util.js):
var inherits = Rx.internals.inherits = function (child, parent) {
function __() { this.constructor = child; }
__.prototype = parent.prototype;
child.prototype = new __();
};
Я потратил некоторое время на изучение цепочек прототипов и думаю, что эта функция позволяет нам "соединить" дочерний и родительский объекты и создать дочерний объект с наследованием. Но я также заметил, что если я создаю родительский объект, его конструктор, его конструктор будет связан с дочерней функцией. Я неправильно понимаю эту функцию (что это правильный способ наследования, и я неправильно связываю объекты)?
2 ответа
Но я также заметил, что если я создаю родительский объект, его конструктор, его конструктор будет связан с дочерней функцией.
Это не правильно. Цепочка прототипов идет в одном направлении, а родительский элемент никак не изменяется.
Единственное предостережение в этой реализации - то, что конструктор родителя не вызывается при вызове конструктора потомка. Реализация класса ES6 обеспечивает это.
На вашей диаграмме есть пара вопросов:
Пример
__
не имеет свойства конструктора, указывающего на__
функция. На самом деле самая причина__
Существует для установки свойства конструктора для дочернего конструктора.__proto__
дочернего экземпляра будет экземпляром__
(Обратите вниманиеnew __()
в коде наследует) и__proto__
из этого будетparent.prototype
,
Таким образом, утилита наследования внедряет мета-объект в цепочку прототипов, единственной целью которого является обеспечение того, чтобы constructor
свойство указывает на правильный класс в дочернем экземпляре.
Может быть полезно сравнить это с тем, как babel реализует семантику класса ES6:
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
Он использует тот факт, что Object.create занимает секунду propertiesObject
аргумент, который может быть использован для добавления дополнительного свойства, которое в этом случае оказывается constructor
, Объект, возвращаемый Object.create
служит той же цели, что и __
экземпляр возвращен new __()
в коде RxJS.
Объекты и Конструкторы
В javascript нужно привыкнуть к тому, что конструктор - это класс. В то время как ES6 действительно представил class
Ключевое слово и синтаксис класса это просто синтаксический сахар базового механизма. Архитектура не изменилась.
Прототип - это свойство конструкторов, которое конструктор будет использовать для создания новых объектов. Так, например, если вы хотите создать много "людей", вы должны написать что-то вроде этого:
function Person (name) {
this.name = name;
}
Person.prototype = {};
Person.prototype.name = "";
Person.prototype.age = null;
Person.prototype.gender = null;
Обратите внимание на несколько вещей. Это в основном перевернуто по сравнению с другими ОО языками. В других языках вы определяете класс, а затем можете определить конструктор как специальное свойство / метод класса. В javascript вы определяете конструктор, а затем определяете класс (прототип) как специальное свойство этого конструктора.
Во-вторых, здесь нет особенных constructor
ключевое слово. Конструктор - это просто обычная функция. В этом нет ничего особенного. Он становится конструктором, только если вы вызываете его, используя new
ключевое слово:
var x = Person(); // regular function, x is undefined
var y = new Person(); // constructor, y is an instance of Person
Таким образом, поскольку ничто не может сказать программисту, что функция является конструктором или обычная функция, программисты javascript разработали соглашение, в котором имена функций всегда начинаются с нижнего регистра, а имена конструкторов всегда начинаются с верхнего регистра. Из приведенного выше кода вы видите функцию с именем Person
так что вы можете предположить, что я собираюсь сделать это конструктором.
наследование
Поскольку прототип - это, ну... прототип объекта, то для наследования от конструктора вы устанавливаете дочерний прототип в экземпляр прототипа Parent. В современном JS вы бы сделали это:
function Employee (name, job) {
this.name = name;
this.job = job;
}
Employee.prototype = Object.create(Person.prototype); // Inherit!
Employee.prototype.job = null;
Обратите внимание, что мы наследуем от объекта (прототипа), потому что в javascript вы наследуете от объектов, а не конструкторов и не классов.
Во-вторых, обратите внимание, что мы наследуем, устанавливая наш прототип в копию прототипа нашего родителя. Это потому, что если мы просто назначим прототип нашего родителя нашему собственному, то когда мы добавим к нему новые свойства (например, job
в этом примере) мы не хотим изменять прототип нашего родителя (потому что тогда это не будет наследованием).
За несколько дней до Object.create
функция существует, вы бы вместо этого сделали это:
Employee.prototype = new Person();
Это все еще актуально сегодня, хотя Object.create
как правило, является предпочтительным. Итак, в коде RxJS, когда вы видите это:
child.prototype = new __();
Вот где происходит наследование. Помните, что прототип - это то, что конструктор использует в качестве шаблона для создания нового объекта. Итак, строка выше:
__.prototype = parent.prototype;
Значит у нас теперь есть функция __
который создаст объект, похожий на объект, который создал бы родитель. Так делаю new __()
создаст объект, похожий на вызов родительского конструктора, но без выполнения какой-либо логики, определенной в родительском конструкторе. Так что в основном это делает что-то похожее на Object.create(parent)
;
Наследование - это просто присвоение копии прототипа родителя нашему собственному прототипу. Все остальные сложные биты, приведенные выше, просто готовятся к копированию прототипа родителя.