Как стать автором
Обновить

Комментарии 16

С давних времён использую декораторы в JavaScript

Не могли бы вы привести несколько самых типичных примеров, когда использование декораторов может сильно облегчить нам жизнь. Спасибо.
Добавил немного применений.
Навскидку:
— мемоизация: кэширование результатов вызова «чистой» функции (зависящей только от аргументов и не имеющей побочных эффектов), например, автодополнение поиска с подгрузкой вариантов, пользователь набрал, а потом стер символ — показали уже загруженный чуть раньше результат
— логгирование
— в целом, перехват всех вызовов любой функции с любым исправлением «на лету» аргументов и возвращаемого значения
пример:
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 тоже декорировать надо.
*_cached я написал для примера. На самом деле конечно выгодней заменить функцию в той же переменной.
Паттерн, конечно, удобный и мне нравится. Но как-то не очень часто приходится его использовать.
Эвенты прекрасно справляются с этими всеми задачами.
Вы как-то переусложняете решение так, что «за деревьями не видно леса», и ещё эти комментарии не по делу про замыкания.
Зачем нужно это трёхэтажное вложение в 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
1 Спасибо, теперь я знаю яваскрипт ещё лучше.
Я давно хотел научиться делать алиасы
Теперь умею :)
2 У меня добавляется в конец, а указанная вами функция добавляет в начало.
В Питоне декораторы выполняют ещё одну функцию, никак не покрываемую вашим решением — документирующую, а документацию удобнее читать в начале функции, чем в конце.

Сравните читаемость
@classmethod
def foo(arg1,arg2):
    ...

@accepts(int,int)
@returns(float)
def bar(low,high):
    ...
и аналогичного кода на JavaScript.
var a=functionTypeAssert("%d%d","%f",function(a,b){
    return a/b;
});// функция - объект, где хочу, там и передаю :)
Ну да, только это и остаётся.
Прикольно. Сам регулярно такой подход юзаю.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории