up
This commit is contained in:
parent
3defacc09d
commit
f99574f53b
178 changed files with 530 additions and 271 deletions
165
1-js/9-object-inheritance/11-mixins/article.md
Normal file
165
1-js/9-object-inheritance/11-mixins/article.md
Normal file
|
@ -0,0 +1,165 @@
|
|||
# Примеси
|
||||
|
||||
В 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` в примеси, мы затем можем использовать их во множестве прототипов.
|
||||
|
||||
## Итого
|
||||
|
||||
- Примесь -- объект, содержащий методы и свойства для реализации конкретного функционала.
|
||||
Возможны вариации этого приёма проектирования. Например, примесь может предусматривать конструктор, который должен запускаться в конструкторе объекта. Но как правило просто набора методов хватает.
|
||||
- Для добавления примеси в класс -- её просто "подмешивают" в прототип.
|
||||
- "Подмешать" можно сколько угодно примесей, но если имена методов в разных примесях совпадают, то возможны конфликты. Их уже разрешать -- разработчику. Например, можно заменить конфликтующий метод на свой, который будет решать несколько задач сразу. Конфликты при грамотно оформленных примесях возникают редко.
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue