Комментарии 16
С давних времён использую декораторы в JavaScript
Не могли бы вы привести несколько самых типичных примеров, когда использование декораторов может сильно облегчить нам жизнь. Спасибо.
Навскидку:
— мемоизация: кэширование результатов вызова «чистой» функции (зависящей только от аргументов и не имеющей побочных эффектов), например, автодополнение поиска с подгрузкой вариантов, пользователь набрал, а потом стер символ — показали уже загруженный чуть раньше результат
— логгирование
— в целом, перехват всех вызовов любой функции с любым исправлением «на лету» аргументов и возвращаемого значения
пример:
Смысл в том, что:
— не надо менять код ни самой функции, ни мест ее вызова
— не надо копипастить однотипные куски кода для использования такого расширения в разных функциях, декоратор достаточно написать один раз и потом использовать, строки 3-5 примера выше превратятся в такое:
где «logged» — это тот самый декоратор:
Декоратор мемоизации пишется аналогично и больше не надо писать унылые:
во всех местах, где оно надо.
— мемоизация: кэширование результатов вызова «чистой» функции (зависящей только от аргументов и не имеющей побочных эффектов), например, автодополнение поиска с подгрузкой вариантов, пользователь набрал, а потом стер символ — показали уже загруженный чуть раньше результат
— логгирование
— в целом, перехват всех вызовов любой функции с любым исправлением «на лету» аргументов и возвращаемого значения
пример:
object = {x: 10, add: function(y) {return this.x + y;}}
// хотим отследить все вызовы object.add откуда угодно
object.add = function(f) {
return function() {console.log(arguments); return f.apply(this, arguments)}
}(object.add);
console.log(object.add(2));
Смысл в том, что:
— не надо менять код ни самой функции, ни мест ее вызова
— не надо копипастить однотипные куски кода для использования такого расширения в разных функциях, декоратор достаточно написать один раз и потом использовать, строки 3-5 примера выше превратятся в такое:
object.add = logged(object.add)
где «logged» — это тот самый декоратор:
logged = function(f) {
return function() {console.log(arguments); return f.apply(this, arguments)}
};
Декоратор мемоизации пишется аналогично и больше не надо писать унылые:
cache = {}
function autocomplete(query, callback)
{
if (cache[query]) {
callback(cache[query]);
return;
}
$.get('search', {query: query}, function(result) {
cache[query] = result;
callback(result);
}
}
во всех местах, где оно надо.
(это был ответ на коммент выше)
«и больше не надо писать унылые»
Я в таких случаях пишу что-дь весёлое типа =)
Я в таких случаях пишу что-дь весёлое типа =)
function f_cached(...) {
return cache[query] || cache[query] = f(...);
}
все так и пишут.
но:
— надо заводить свою переменную cache и свою версию *_cached() на каждую такую функцию
— надо не забыть переправить все вызовы f на f_cached, если кэширование прикручивалось не сразу
— для асинхронных функций, как в примере выше, все чуть сложнее, там callback тоже декорировать надо.
но:
— надо заводить свою переменную cache и свою версию *_cached() на каждую такую функцию
— надо не забыть переправить все вызовы f на f_cached, если кэширование прикручивалось не сразу
— для асинхронных функций, как в примере выше, все чуть сложнее, там callback тоже декорировать надо.
Эвенты прекрасно справляются с этими всеми задачами.
Вы как-то переусложняете решение так, что «за деревьями не видно леса», и ещё эти комментарии не по делу про замыкания.
Зачем нужно это трёхэтажное вложение в withVars? Ваш тесткейс проходит и с 2 этажами, и даже без замыкания f (вида var f1=f;).
Первое замыкание тоже нужно не само по себе, а для сохранения объекта arguments.
То есть, переписав по потребностям задачи, получаем:
Или, если нравится присваивание через параметр,
Зачем нужно это трёхэтажное вложение в withVars? Ваш тесткейс проходит и с 2 этажами, и даже без замыкания f (вида var f1=f;).
Первое замыкание тоже нужно не само по себе, а для сохранения объекта arguments.
То есть, переписав по потребностям задачи, получаем:
function withVars(func, some_more_args){
var args = Array.prototype.slice.call(arguments, 1); //для передачи группы аргументов
return function(){
func.apply(null, args.slice.call(arguments, 0).concat(args)); //сцепляем 2 группы аргументов
}
};
Так же читать намного проще?Или, если нравится присваивание через параметр,
function withVars(func, variables){
return (function(a){
return function(){
func.apply(null, a.slice.call(arguments, 0).concat(a));
}
})(Array.prototype.slice.call(arguments, 1))
};
(Но это, думаю, читать сложнее, а делает то же самое.)спасибо, проворонил тот аспект, что локальные переменные функции не меняются
Мне кажется, вы только что изобрели Function.prototype.bind
В Питоне декораторы выполняют ещё одну функцию, никак не покрываемую вашим решением — документирующую, а документацию удобнее читать в начале функции, чем в конце.
Сравните читаемость
Сравните читаемость
@classmethod
def foo(arg1,arg2):
...
@accepts(int,int)
@returns(float)
def bar(low,high):
...
и аналогичного кода на JavaScript.Прикольно. Сам регулярно такой подход юзаю.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Декораторы в JavaScript