8.2 KiB
Примеси
В JavaScript невозможно унаследовать от двух и более объектов. Ссылка __proto__
-- только одна.
Но потребность такая существует -- к примеру, мы написали код, релизующий методы работы с шаблонизатором или методы по обмену событиями, и хочется легко и непринуждённо добавлять эти возможности к любому классу.
Обычно это делают через примеси.
Примесь (англ. mixin) -- класс или объект, реализующий какое-либо чётко выделенное поведение. Используется для уточнения поведения других классов, не предназначен для самостоятельного использования.
Пример примеси
Самый простой вариант примеси -- это объект с полезными методами, которые мы просто копируем в нужный прототип.
Например:
*!*
// примесь
*/!*
var sayHiMixin = {
sayHi: function() {
alert("Привет " + this.name);
},
sayBye: function() {
alert("Пока " + this.name);
}
};
*!*
// использование:
*/!*
function User(name) {
this.name = name;
}
// передать методы примеси
for(var key in sayHiMixin) User.prototype[key] = sayHiMixin[key];
// User "умеет" sayHi
new User("Вася").sayHi(); // Привет Вася
Как видно из примера, методы примеси активно используют this
и предназначены именно для запуска в контексте "объекта-носителя примеси".
Если какие-то из методов примеси не нужны -- их можно перезаписать своими после копирования.
Примесь для событий
Теперь пример из реальной жизни.
Важный аспект, который может понадобиться объектам -- это умение работать с событиями.
То есть, чтобы объект мог специальным вызовом генерировать "уведомление о событии", а на эти уведомления другие объекты могли "подписываться", чтобы их получать.
Например, объект "Пользователь" при входе на сайт может генерировать событие "login"
, а другие объекты, например "Календарь" может такие уведомления получать и подгружать информацию о пользователе.
Или объект "Меню" может при выборе пункта меню генерировать событие "select"
с информацией о выбранном пункте меню, а другие объекты -- подписавшись на это событие, будут узнавать об этом.
События -- это средство "поделиться информацией" с неопределённым кругом заинтересованных лиц. А там уже кому надо -- тот среагирует.
Примесь eventMixin
, реализующая события:
var eventMixin = {
/**
* Подписка на событие
* Использование:
* menu.on('select', function(item) { ... }
*/
on: function(eventName, handler) {
if (!this._eventHandlers) this._eventHandlers = {};
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
this._eventHandlers[eventName].push(handler);
},
/**
* Прекращение подписки
* menu.off('select', handler)
*/
off: function(eventName, handler) {
var handlers = this._eventHandlers && this._eventHandlers[eventName];
if (!handlers) return;
for(var i=0; i<handlers.length; i++) {
if (handlers[i] == handler) {
handlers.splice(i--, 1);
}
}
},
/**
* Генерация события с передачей данных
* this.trigger('select', item);
*/
trigger: function(eventName /*, ... */) {
if (!this._eventHandlers || !this._eventHandlers[eventName]) {
return; // обработчиков для события нет
}
// вызвать обработчики
var handlers = this._eventHandlers[eventName];
for (var i = 0; i < handlers.length; i++) {
handlers[i].apply(this, [].slice.call(arguments, 1));
}
}
};
Здесь есть три метода:
.on(имя события, функция)
-- назначает функцию к выполнению при наступлении события с данным именем. Такие функции хранятся в защищённом свойстве объекта_eventHandlers
..off(имя события, функция)
-- удаляет функцию из списка предназначенных к выполнению..trigger(имя события, аргументы)
-- генерирует событие, при этом вызываются все назначенные на него функции, и им передаются аргументы.
Использование:
// Класс Menu с примесью eventMixin
function Menu() {
// ...
}
for(var key in eventMixin) {
Menu.prototype[key] = eventMixin[key];
}
// Генерирует событие select при выборе значения
Menu.prototype.choose = function(value) {
*!*
this.trigger("select", value);
*/!*
}
// Создадим меню
var menu = new Menu();
// При наступлении события select вызвать эту функцию
*!*
menu.on("select", function(value) {
alert("Выбрано значение " + value);
});
*/!*
// Запускаем выбор (событие select вызовет обработчики)
menu.choose("123");
...То есть, смысл событий -- обычно в том, что объект, в процессе своей деятельности, внутри себя (this.trigger
) генерирует уведомления, на которые внешний код через menu.on(...)
может быть подписан. И узнавать из них ценную информацию о происходящем, например -- что выбран некий пункт меню.
Один раз написав методы on/off/trigger
в примеси, мы затем можем использовать их во множестве прототипов.
Итого
- Примесь -- объект, содержащий методы и свойства для реализации конкретного функционала. Возможны вариации этого приёма проектирования. Например, примесь может предусматривать конструктор, который должен запускаться в конструкторе объекта. Но как правило просто набора методов хватает.
- Для добавления примеси в класс -- её просто "подмешивают" в прототип.
- "Подмешать" можно сколько угодно примесей, но если имена методов в разных примесях совпадают, то возможны конфликты. Их уже разрешать -- разработчику. Например, можно заменить конфликтующий метод на свой, который будет решать несколько задач сразу. Конфликты при грамотно оформленных примесях возникают редко.