Neutrino — крошечный js-фреймворк с полноценным наследованием и событиями

в 15:57, , рубрики: framework, javascript, library, нейтрино, метки: , , , ,

В проектах с небольшим фронтэндом бывает не всегда разумно использовать тяжелые фреймворки вроде 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js