Коллеги, использующие Backbone.js! Вы задумывались, как работает наследование в этой библиотеке?
Знаете, как себя ведет Backbone.Model.extend({})
?
И наверняка знаете и помните, что у extend два опциональных параметра: proto props и static props.
Если хотя бы на один выше заданный вопрос вы ответили отрицательно — прошу под кат.
Постараюсь порадовать пошаговым исследованием, схемками, табличками и примерами.
Предисловие
Лично для меня понимание того, как работает используемая библиотека/компонент (особенно та, которую использую чаще всего) является чем-то обязательным и важным. Почему?
- Можно узнать много нового, полезного и интересного из исходников сторонней популярной библиотеки
- Риск застрять на проблеме/баге, плотно связанной с работой библиотеке сводятся к минимуму
- Есть небольшой шанс форкнуть оупенсорс библиотеку и внести свою лепту
Уже давно меня интересовал вопрос наследования в Backbone.js
И вот я решился, открыл исходники, и… приступим!
Сразу же оговорюсь: наследование работает одинаково для всех сущностей библиотеки, будь то Model, View, Collection или Router. В данной статье я для примера буду использовать View.
Документация
С документации нам известно, что метод extend имеет два параметра: properties и classProperties.
Backbone.View.extend(properties, [classProperties])
Также можно понять, что только classProperties — параметр необязательный, но на самом деле и properties является таковым.
И если первый аргумент нормально описан для всех сущностей, то о предназначении второго (classProperties) нам намекают лишь в документации к Model и Collection:
To create a Collection class of your own, extend Backbone.Collection, providing instance properties, as well as optional classProperties to be attached directly to the collection's constructor function
Смотрим исходники
Подобно деббагеру developer tools, пройдемся по методу extend.
Испытуемый код:
Backbone.View.extend({
testProp1: true
}, {
testProp2: true
});
Этап #1
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var extend = function(protoProps, staticProps) {
var parent = this;
var child;
Из комментариев видим, что это аналог goog.inherits (с которым мне не приходилось сталкиваться) с некоторыми изменениями.
На первом этапе у нас есть:
- protoProps: первый аргумент функции
- staticProps: второй аргумент функции
- parent: в данном случае это конструктор Backbone.View
- child: сущесвто, неизвестное нам пока что
Сразу же считаю необходимым разъяснить два момента:
1. Backbone.View Constructor — это функция-конструктор, которая служит для обработки опций, подписки на события, вызова метода initialize и прочих действий, которые являются очень важными в работе библиотеки и происходят «за кулисами».
2. Backbone.View.prototype — это набор стандартных методов и свойств: render, remove и т.п.
Этап #2
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
С неизвестным ранее существом child происходит кое-что интересное. Оно превращается либо в заданную функцию в первом параметре (protoProps) constructor, либо в Backbone.View Constructor. Пока что не зацикливаемся на этом и идем дальше.
Этап #3
_.extend(child, parent, staticProps);
Расширяем child всеми свойствами и методами Backbone.View Constructor и нашими staticProps.
На самом деле у конструктора Backbone.View нет никаких свойств и методов, скорее всего это сделано на всякий случай. Мало ли, появятся когда-нибудь, и extend не нужно будет переписывать.
Этап #4
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
Появляется некий Surrogate. Это prototype нашего с вами child, у которого свойство constructor — это child (да да, он же) и prototype — Backbone.View.prototype.
Его предназначение в том, что вместо расширения child'a свойствами и методами Backbone.View.prototype мы просто добавляем их в качестве прототипа, тем самым экономим драгоценную память.
Этап #5
if (protoProps) _.extend(child.prototype, protoProps);
child.__super__ = parent.prototype;
return child;
Добавляем к прототипу child (Surrogate) свойства и методы из protoProps (первый аргумент функции extend) и создаем свойство __super__, которое будет ссылкой на Backbone.View.prototype.
В итоге мы получаем жирненький конструктор (child), который выглядит примерно так:
Вывод
Backbone оказался более гибким, чем я думал.
Используя extend, мы можем задать, помимо стандартных свойств, статические. Они будут доступны для использования в контексте конструктора а также через свойство constructor.
Также мы можем легко использовать свой конструктор, лишь задав его в свойстве constructor в первом параметре метода extend.
Во второй части статьи мы подробно рассмотрим возможности, которые дает нам такой принцип наследования.
Автор: Leestex