Декоратор – приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение.
Декоратор получает функцию и возвращает обертку, которая делает что-то своё «вокруг» вызова основной функции.
function bind(func, context) {
return function() {
return func.apply(context, arguments);
};
}Вызов bind(func, context) возвращает обёртку, которая ставит this и передаёт основную работу функции func.
Декоратор для замера скорости выполнения функций:
var timers = {};
// прибавит время выполнения f к таймеру timers[timer]
function timingDecorator(f, timer) {
return function() {
var start = performance.now();
var result = f.apply(this, arguments); // (*)
if (!timers[timer]) timers[timer] = 0;
timers[timer] += performance.now() - start;
return result;
}
}
// функция может быть произвольной, например такой:
var fibonacci = function f(n) {
return (n > 2) ? f(n - 1) + f(n - 2) : 1;
}
// использование: завернём fibonacci в декоратор
fibonacci = timingDecorator(fibonacci, 'fibo');
// неоднократные вызовы...
alert( fibonacci(10) ); // 55
alert( fibonacci(20) ); // 6765
// ...
// в любой момент можно получить общее количество времени на вызовы
alert(timers.fibo + 'мс');На строке (*) внутри декоратора осуществляется передача вызова:
Этот приём называется «форвардинг вызова» (от англ. forwarding): текущий контекст и аргументы через apply
передаются в функцию f, так что изнутри f всё выглядит так, как была вызвана она напрямую, а не декоратор.
Декоратор для проверки типа аргументов:
// вспомогательная функция для проверки на число
function checkNumber(value) {
return typeof value == 'number';
}
// декоратор, проверяющий типы для f
// второй аргумент checks - массив с функциями для проверки
function typeCheck(f, checks) {
return function() {
for (var i = 0; i < arguments.length; i++) {
if (!checks[i](arguments[i])) {
alert( "Некорректный тип аргумента номер " + i );
return;
}
}
return f.apply(this, arguments);
}
}
function sum(a, b) {
return a + b;
}
// обернём декоратор для проверки
sum = typeCheck(sum, [checkNumber, checkNumber]); // оба аргумента - числа
// пользуемся функцией как обычно
alert( sum(1, 2) ); // 3, все хорошо
// а вот так - будет ошибка
sum(true, null); // некорректный аргумент номер 0
sum(1, ["array", "in", "sum?!?"]); // некорректный аргумент номер 1
Допустим, мы хотим выводить элементы массива, обёрнутые в хтмл теги.
В данном способе - мы передаём тег в аргумент функции.
var weekdays = ["понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье"];
function getDay(num) {
return weekdays[num];
}
// декоратор
function wrapDecorator(f, startTag, closeTag) {
return function(num) {
return startTag + f.apply(this, arguments) + closeTag;
};
}
// Использование декоратора
var getDayInDiv = wrapDecorator(getDay, "<div>", "</div>");
for (var i = 0; i < 7; i++) {
console.log(getDayInDiv(i));
}Результат:
<div>понедельник</div>
<div>вторрник</div>
<div>среда</div>
<div>четверг</div>
<div>пятница</div>
<div>воскресенье</div>