en.javascript.info/1-js/9-object-inheritance/18-mixins/article.md
Ilya Kantor d4c714cbe1 work
2016-08-05 16:53:08 +03:00

165 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Примеси
В JavaScript невозможно унаследовать от двух и более объектов. Ссылка `__proto__` -- только одна.
Но потребность такая существует -- к примеру, мы написали код, релизующий методы работы с шаблонизатором или методы по обмену событиями, и хочется легко и непринуждённо добавлять эти возможности к любому классу.
Обычно это делают через примеси.
Примесь (англ. mixin) -- класс или объект, реализующий какое-либо чётко выделенное поведение. Используется для уточнения поведения других классов, не предназначен для самостоятельного использования.
<!--break-->
## Пример примеси
Самый простой вариант примеси -- это объект с полезными методами, которые мы просто копируем в нужный прототип.
Например:
```js run
*!*
// примесь
*/!*
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`, реализующая события:
```js
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));
}
}
};
```
Здесь есть три метода:
1. `.on(имя события, функция)` -- назначает функцию к выполнению при наступлении события с данным именем. Такие функции хранятся в защищённом свойстве объекта `_eventHandlers`.
2. `.off(имя события, функция)` -- удаляет функцию из списка предназначенных к выполнению.
3. `.trigger(имя события, аргументы)` -- генерирует событие, при этом вызываются все назначенные на него функции, и им передаются аргументы.
Использование:
```js
// Класс 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` в примеси, мы затем можем использовать их во множестве прототипов.
## Итого
- Примесь -- объект, содержащий методы и свойства для реализации конкретного функционала.
Возможны вариации этого приёма проектирования. Например, примесь может предусматривать конструктор, который должен запускаться в конструкторе объекта. Но как правило просто набора методов хватает.
- Для добавления примеси в класс -- её просто "подмешивают" в прототип.
- "Подмешать" можно сколько угодно примесей, но если имена методов в разных примесях совпадают, то возможны конфликты. Их уже разрешать -- разработчику. Например, можно заменить конфликтующий метод на свой, который будет решать несколько задач сразу. Конфликты при грамотно оформленных примесях возникают редко.