19 August 2013

Наследование в Backbone.js

Website developmentJavaScript
Коллеги, использующие 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), который выглядит примерно так:


Вывод и примеры использования


Используя extend, мы можем задать, помимо стандартных свойств, статические. Они будут доступны для использования в контексте конструктора а также через свойство constructor.:
var ViewConstructor = Backbone.View.extend({
    protoMehod: function () {
        console.log(this.constructor.staticMethod());
    }
}, {
    staticProperty: 5,
    staticMethod: function () {
        return 'Hello from constructor. Property is ' + this.staticProperty;
    }
});
var viewInstance = new ViewConstructor();

Контекстом для методов во втором объекте-аргументе (staticProps) будет ViewConstructor. Для методов в первом — viewInstance.

Также мы можем легко использовать свой конструктор, лишь задав его в свойстве constructor в первом параметре метода extend:
Backbone.View.extend({
    constructor: function () {
        // Не забываем перенести необходимую логику из конструктора Backbone.View
        this.staticMethod();
    },
    render: function () {
        this.constructor.staticProperty; // 5
    }
}, {
    staticProperty: 5,
    staticMethod: function () {
        return 'Hello from constructor. Property is ' + this.staticProperty;
    }
});

В данном случае, при создании экзепляра при помощи оператора new будет использоваться переопределенный в первом параметре конструктор, для которого будут доступны все статические свойства, указанные во втором параметре, а так же все методы из Backbone.View.prototype.
Tags:backbonebackbone.jsextend
Hubs: Website development JavaScript
+5
13.4k 58
Comments 1