С давних времён использую декораторы в JavaScript. Недавно увидел хабротопик про примеси, который натолкнул меня на мысль поделиться собственным опытом, ибо технологии немного похожие.
Реализации, предлагаемые по первым ссылкам в Google, работают не тем образом, как это работает в Python. Во многих статьях предлагается создать объект, заполнить его поля функциями и осуществлять вызовы отдекорированных через эти поля.
На первой странице есть несколько ссылок, где используются методы, сходные с моими,
Есть ещё много реализаций, но они мне неинтересны.
Расскажу наиболее правильную с моей точки зрения.
Итак, что такое декоратор?
Декоратор, это функция, которая добавляет функции-аргументу функционала.
Пример из Python:
Что мы тут делаем?
Мы тут создаём функцию, которая создаёт другую функцию, которая выполняет то, что нам нужно, и вызывает декорируемую функцию.
Итак, мы хотим вызвать функцию, передав ей функцию и, возможно, дополнительные параметры, и получить отдекорированную функцию b, т.е.
Приведу участок кода из одной своей библиотеки.
И применение:
В итоге в нескольких строках кода получаем мощный эффект.
Лёгким движением руки функцию можно переделать таким образом, чтобы параметр добавлялся в начало. Для этого нужно просто заменить
на
JavaScript.ru — Замыкания
arguments — MDN
apply — MDN
P.S. Спасибо НЛО за инвайт.
UPD1: Добавил несколько применений.
UPD2: немного исправил код
Что меня не устраивает в известных реализациях?
Реализации, предлагаемые по первым ссылкам в Google, работают не тем образом, как это работает в Python. Во многих статьях предлагается создать объект, заполнить его поля функциями и осуществлять вызовы отдекорированных через эти поля.
На первой странице есть несколько ссылок, где используются методы, сходные с моими,
Есть ещё много реализаций, но они мне неинтересны.
Расскажу наиболее правильную с моей точки зрения.
Нормальный декоратор
Итак, что такое декоратор?
Декоратор, это функция, которая добавляет функции-аргументу функционала.
Пример из Python:
def superfunc(n=2):
print("Megclosure created")
def clos(func):
print("Megclosure used")
def clos1(*args, **kwargs):
print("Megclosured function",func.__name__)
res = func(*args, **kwargs)
return (res**n)
return clos1
return clos
def a(par):
return par+9;
b = (superfunc())(a)
@superfunc(3)
def c(txt):
return len(txt)+1
print ( b(1),c("abc"*3))
Что мы тут делаем?
Мы тут создаём функцию, которая создаёт другую функцию, которая выполняет то, что нам нужно, и вызывает декорируемую функцию.
Как это будет на JavaScript
Итак, мы хотим вызвать функцию, передав ей функцию и, возможно, дополнительные параметры, и получить отдекорированную функцию b, т.е.
function decorator(){
.....
}
.....
function a(){
......
}
......
var b=decorator(a, arg1, arg2,....., argN);
.Приведу участок кода из одной своей библиотеки.
function withVars(f, variables) {
var args = Array.prototype.slice.call(arguments, 1);// получаем массив аргументов функции, за исключением самой функции, причём именно МАССИВ
return function () {// замыкаем f и args, создаём отдекорированную функцию
var p = arguments;// мы к arguments ничего добавить не сможем ...
Array.prototype.push.apply(p, args);//... поэтому добавляем к p
f.apply(this, p);// вызываем функцию f в контексте this с параметрами p
}
};
И применение:
function a(){
var str ="";
for(var i=0;i<arguments.length;i++){
str+=arguments[i]+" ";
}
alert(str);
}
a(1,2,4,5,"six");//1 2 3 4 5 six
var b=withVars(a,4,8,15,16,23,42);
b(1,2,4,5,"six");//1 2 3 4 5 six 4 8 15 16 23 42
В итоге в нескольких строках кода получаем мощный эффект.
Лёгким движением руки функцию можно переделать таким образом, чтобы параметр добавлялся в начало. Для этого нужно просто заменить
Array.prototype.push.apply(p, args);
f.apply(this, p);
на
Array.prototype.push.apply(args,p);
f.apply(this, args);
.Возможные применения
- Допустим вам нужно вызвать callback2 из callback1,callback3 из callback2, и т.д., и при этом передать некоторые данные из одного колбека в другой. Мы не можем передать дополнительную информацию в объекте-источнике событий. Например, когда мы используем GM_xmlhttpRequest. Короче, источник событий спрятан от нас, а наружу торчит функция, которую мы можем вызывать, но которая не сохраняет произвольную информацию для передачи в коллбек. Событий будет много и возникать они будут возникать «параллельно», так что создать переменную — не вариант.
Тогда мы делаем так:
.function cb1(evt){ ....... GM_xmlhttpRequest({ method: "GET", url: url2, onload: withVars(cb2,processed2) }); } function cb2(evt){ ....... GM_xmlhttpRequest({ method: "GET", url: url3, onload: withVars(cb3,processed3) }); } function cb3(evt){ ....... GM_xmlhttpRequest({ method: "GET", url: url4, onload: withVars(cb4,processed4) }); } function cb3(evt){ ..... } GM_xmlhttpRequest({ method: "GET", url: url1, onload: cb1 });
Решение получше многих.
- Валидация данных.
Например, можно написать декоратор, который будет проверять соответствие типа аргументов функции определённой форматной строке, а если не соответствует, то конвертировать/бросать ошибку.
Тогда можно будет писать код вроде следующего:
.var mult=fstrValidate(function(a,b){ return a*b; },"%d%d");
- Ограничение частоты исполнения функции
- Удобство. Во многих случаях код с использованием декораторов будет чище, красивее и эффективнее.
- Применения ограничены вашей фантазией. Если задача не хочет решаться / некрасиво решается «в лоб», то возможно её можно решить с помощью декораторов.
Что почитать
JavaScript.ru — Замыкания
arguments — MDN
apply — MDN
P.S. Спасибо НЛО за инвайт.
UPD1: Добавил несколько применений.
UPD2: немного исправил код