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

Декораторы в JavaScript

Время на прочтение3 мин
Количество просмотров9.4K
С давних времён использую декораторы в JavaScript. Недавно увидел хабротопик про примеси, который натолкнул меня на мысль поделиться собственным опытом, ибо технологии немного похожие.

Что меня не устраивает в известных реализациях?


Реализации, предлагаемые по первым ссылкам в 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: немного исправил код
Теги:
Хабы:
+25
Комментарии16

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн