В проектах с небольшим фронтэндом бывает не всегда разумно использовать тяжелые фреймворки вроде backbone, ember или knockout. Тем не менее потребность использовать модели, наследование и качественное взаимодействие между ними остается. Предлагаю вашему вниманию фреймворк Neutrino, который делает все вышеперечисленное, при этом его размер не превышает 100 строк кода.
Приветствуется любой фидбек по фреймворку.
Простую демку с наследованеим и событиями можно посмотреть тут.
Создание класса
Для создания класса вызываем метод 'extend' объекта _neutrino.Root'. В качестве аргумента передаем ему объект, который содержит методы и статические элементы будущего класса.
var Model = _n.Root.extend({
//methods and static members
});
Метод extend возвращает конструктор класса.
Создание экземпляров класса
Чтобы создать экземпляр класса мы просто вызываем его конструктор с ключевым словом new и передаем всего один аргумент. Этот аргумент должен быть объектом, содержащим все необходимые данные. Содержимое объекта будет записано в поле data свежесозданного объекта.
var model = new Model({
id: 1
});
alert(model.data.id); //1
Дефолтные значения
Для любого свойства объекта данных мы можем задать дефолтное значение:
var Model = _n.Root.extend({
defaults: {
id: "default id"
}
});
var model = new Model();
alert(model.data.id); //"default id"
Конструкторы
Мы можем задать псевдо-конструктор для любого класса. Функция псевдо-конструктора называется construct. Ее поведение полностью эквивалентно обычному конструктору — вызывается при создании объекта, может вернуть значение, которое будет возвращено как результат работы оператора new. В коде псевдо-конструктора надо вызывать родительский метод с помощью this._superCall();
var Model = _n.Root.extend({
construct: function() {
this._superCall();
alert("Hello world");
}
});
Наследование
Нейтрино поддерживает полноценное наследование:
- Можно создавать дочерние классы, используя метод extend базового класса;
- Конструктор любого класса содержит свойство superclass, напрямую указывающее на конструктор родительского класса;
- Из любого метода можно легко вызывать ближайший имеющийся родительский метод;
- Поле defaults автоматически мерджится со всеми родительскими данными.
Давайте посмотрим на простой пример:
var Parent = _n.Root.extend({
defaults: {
a: 1,
b: 2
},
construct: function() {
this._superCall();
alert(this.getSum());
},
getSum: function() {
return this.data.a + this.data.b;
}
});
var Child = Parent.extend({
defaults: {
a: 10,
c: 3
},
getSum: function() {
return this.superCall() + this.data.c;
}
});
new Parent(); //alert(1 + 2);
new Child(); //alert(10 + 2 + 3);
Вызов родительских методов
Изнутри метода есть два способа вызова ближайшего родительского метода. Если нужно вызвать родительский метод с теми же параметрами, с какими был вызван текущий метод, можно использовать this._superCall();
...
someMethod: function(a, b, c) {
return this._superCall(); //call closest parent method with a,b,c parameters
}
...
Если родительский метод нужно вызвать с другими параметрами, нужно использовать прямую ссылку на ближайший родительский метод – this._superMethod;
...
someMethod: function(a, b, c) {
return this._superMethod(a + 1, b + 1, c + 1); //call closest parent method with changed parameters
}
...
Фактически, this.superCall(); это сокращенный эквивалент this._superMethod.apply(this, arguments);
События
События – это отлиный способ организовать взаимодействие моделей… Модели в Neutrino могут генерировать события и подписываться на события другизх моделей.
Генерация событий
Чтобы сгенерировать событие в модели, нужно вызвать ее метод fireEvent. Ему нужно передать два параметра – имя события eventName и (опционально) объект info, содержащий любую дополнительную информацию.
model.fireEvent("item-added", {
item: item
//some additional information
});
Обработка собственных событий
Когда модель генерирует событие, в первую очередь вызывается ее собственный метод onOwnEvent, который получает параметры eventName и info.
/* Model class initialization*/
...
onOwnEvent: function(eventName, info) {
switch(eventName) {
case "item-added":
//increase items counter
//render info.item
break;
}
}
...
model.fireEvent("item-added", {
item: item
});
Подписка на события
Для подписки на события модели нужно вызвать ее метод subscribe и передать ему два параметра – функцию-обработчик и имя события, на которое вы хотите подписаться. Если не задавать имя события (или передать звездочку * в качестве имени события), то ваш обработчик будет вызываться при генерации любого события, В противном случае – только при событии, на которое вы подписались.
Естественно, обработчик при вызове получет параметры eventName и info.
function commonHandler(eventName, info) {
switch(eventName) {
case "e1":
alert("e1 fired");
break;
}
}
function specificHandler(eventName, info) {
alert("e1 fired");
}
model.subscribe(commonHandler);
model.subscribe(specificHandler, "e1");
model.fireEvent("e1"); //info in handlers will be set to undefined
Обработка событий методами модели
Обычно, возникает необходимость обраьботать событие одной модели методом какой-нибудь другой модели. Чтобы сохранить контекст this и, возможно, какие-то другие данные, актуальные в момент подписки, можно, конечно, использовать замыкание:
/* class initialization stuff */
...
processItem: function(item) {
var self = this;
item.subscribe(function(eventName, info) {
self.onItemEvent(eventName, info, item);
});
},
onItemEvent: function(item, eventName, info) {
...
}
Однако, Neutrino предоставляет более простой способ сделать это.
item.subscribe(this.onItemEvent.bind(this, item));
К каждому методу модели в Neutrino приделана функция bind(), которая вызовет ваш метод с правильным контекстом this и любыми дополнительными параметрами.
Прояснить понимание поможет простой пример:
var binding = this.onItemEvent.bind(this, 1, 2));
setTimeout(function() {
binding(3, 4); //your method onItemEvent will be called with arguments 1, 2, 3, 4.
});
Используя bind(), вы сначала указывааете желаемый контекст (обычно this), затем — любое количество дополнительных параметров (a, b). Когда мы вызываем функцию-результат привязки с параметрами (c, d), то, в конце концов, мы придем к вызову нашего метода с параметрами (a, b, c, d).
Приветствуется любой фидбек по фреймворку.
Автор: alevkon