27 июля 2009, 23:59
Hard JavaScript: декораторы (decorators)
Декоратор — образно говоря, это нечто, что оборачивает другое нечто %))) Вот например есть функция и мы можем создать для неё декоратор, который будет запускать какую-то другую функцию в начале или в конце выполнения «родителя». Для чего-же?: вариантов миллион ;) Например для проверки данных, если этой проверки ранние не было, или для логгирования, дебагинга, или для изменения общего поведения ;)
Перейдём к делу %) Создадим помощник, который будет нам помогать делать чудеса.
Приступим к декораторам ;)
Теперь бонусы
Перейдём к делу %) Создадим помощник, который будет нам помогать делать чудеса.
Function.prototype.method = function(methodName, f) { if (typeof f != "undefined") this.prototype[methodName] = f; return this.prototype[methodName]; }Что-что? Спросите вы! Образно говоря, мы легко добавляем новый (если существует, возвращаем старый) метод (функцию) в прототип заданной функции о_О Прототип?? Хе-хе! Прототип — это свойства и методы функции, класса, массива, любого объекта, которые существуют по умолчанию. :D Да, они ограничены (прелести в прототипе), и их есть стандартный набор, потому мы можем их добавлять переназначать или удалять ;)
Приступим к декораторам ;)
// Теперь мы можем достать как ближайшую "украшенную" ф-цию, так и самую первую в цепочке Function.method("restore", function(fullRestore){ var ret = this.old || this; while (fullRestore && ret.old) { ret = ret.old; } return ret; }) // специально для декорирования методов, востановления имён Function.method("decorateMethod", function(methodName, decorator){ var f = this.method(methodName); if (!f) return null; f.name = methodName; f = f.decorate(decorator); return this.method(methodName, f); }) Function.method("decorate", function(decorator){ // Сохраняем исходную функцию var oldFunc = this; // Важно! теперь возможно нормальное пере-использование одного и того же декоратора. // Теперь мы его никак не трогаем и не изменяем, а создаём и возвращаем новую ф-цию. // Однако теперь декоратор первым аргументом всегда будет получать некий объект, // в котором -- original: oldFunc (оригинал) и decoratorInstance: f (настоящий декоратор) var f = function(){ return decorator.apply(this, [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments))) } // Сохраняем оригинал ф-ции - в decoratorInstance f f.old = oldFunc; // Восстанавливаем прототип и конструктор ф-ции. // Это необходимо для сохранения ф-ции как конструктора. f.prototype = this.prototype; f.prototype.constructor = f; // Восстанавливаем имя ф-ции. Откуда оно вообще берётся? Можно задать вручную. // Или см. выше, новый метод decorateMethod: в нём это задаётся. f.name = oldFunc.name; // возвращаем декоратор return f; })Всем понравилось, теперь пример
// Простая функция-декоратор function docwrite() { var types = arguments; // передаём типы return function(dScope) { // получаем аргументы родителя (воруем) var original = arguments[0].original; // или можно dScope.original var arguments = Array.prototype.slice.call(arguments, 1); var params = arguments; // выводим наш текст, который мы алертим document.write(params[0]); // незабываем позвать родителя, а то не красиво ;) return original.apply(this, arguments); // в результате видем ещё и алерт } } // надеваем декоратор на алерт alert = alert.decorate(docwrite());Дальше в body пишем
<script type="text/javascript"> alert("y0"); </script>
Теперь бонусы
// Кикнуть декоратор можно так ;) Function.method("recover", function() { return this.old || this; }) // Повесить обработку "до" Object.method('before', function(methodName, f){ var method = listenerInit.call(this, methodName); if (method) method.listenersBefore.push(f); }) // Повесить обработку "после" Object.method('after', function(methodName, f){ var method = listenerInit.call(this, methodName); if (method) method.listenersAfter.push(f); }) // что бы повесить декоратор "до" и "после", нам нужен вот такой обработчик (слушатель) function listenerInit(methodName) { var method = this[methodName]; if (typeof method != "function") return false; // продекорировано, или ещё нет? if (!method.listenable) { this[methodName] = method.decorate(function(){ var decorator = arguments.callee; decorator.listenable = true; var list = decorator.listenersBefore; for (var i = 0, l = list.length; i < l; i++) { if (typeof list[i] == "function" && list[i].apply(this, arguments) === false) return; } var ret = decorator.old.apply(this, arguments); list = decorator.listenersAfter; for (var i = 0, l = list.length; i < l; i++) list[i].apply(this, arguments); return ret; }); method = this[methodName]; } method.listenersBefore = method.listenersBefore instanceof Array ? method.listenersBefore : []; method.listenersAfter = method.listenersAfter instanceof Array ? method.listenersAfter : []; return method; }

2 комментария РСС
Изначально блог задумывался как «для себяшный сборник знаний», но на него начал ходить народ ;)
Но у нас отродясь это не называли декораторами. По-русски принято называть их обертками.
* всё одно и тоже ;)
Ваш комментарий