Зачастую бывает полезно добавить какую-нибудь дополнительную логику в код, которая собирает данные по ходу работы приложения, например подсчет количества вызовов, или обработка ошибок. Но портить существующий компактно написанный код (если конечно у вас есть такое счастье) не очень-то хочется. Решение в виде приемов АОП существует уже давно, и широко применятся. На платформах .NET и Java многие АОП фреймворки ориентируются на применение атрибутов к методам и классам. Выглядит код почти неизмененным, а в распоряжении оказывается достаточно мощный механизм расширения функциональности.
В JavaScript таких фреймворков не так много, и те, которые я успел найти, при расширении функций походили на обыкновенную подписку на события. В общем, не совсем понравился синтаксис, захотелось чего-нибудь простого, и более приближенного к «высоким материям» .NET и Java.
Поставим перед собой типичные задачи. Необходимо добавить логику:
• перед выполнением функции
• после выполнения функции
• в случае возникновения исключения
Мне ближе .NET атрибуты, да и реализация такого синтаксиса напрашивается сама собой, поэтому исходить будем из небольшого примера на C#
Немного модифицируем код, чтобы он был валиден для JavaScript
В принципе, ничего сложного мы не написали, и всё выглядят вполне реализуемым. Осталось реализовать функцию AOP и грамотно организовать расширение логики.
Организуем расширение функциональности
И добавим необходимые нам атрибуты
Все готово, можно создавать методы и расширять их
И теперь можно «элегантно» следить за функциями, помечая их одной строчкой. Можно доработать функцию AOP() и применять атрибуты еще и на объекты, но это будет уже немного иная задача.
В статье на википедии есть ссылки на реализации АОП для JavaScript, можно сравнить. И еще один проект Aspect JS
PS: Сделали справедливое замечание по поводу eval. Вернул кусок кода, который посчитал лишним в данной статье.
В JavaScript таких фреймворков не так много, и те, которые я успел найти, при расширении функций походили на обыкновенную подписку на события. В общем, не совсем понравился синтаксис, захотелось чего-нибудь простого, и более приближенного к «высоким материям» .NET и Java.
Поставим перед собой типичные задачи. Необходимо добавить логику:
• перед выполнением функции
• после выполнения функции
• в случае возникновения исключения
Мне ближе .NET атрибуты, да и реализация такого синтаксиса напрашивается сама собой, поэтому исходить будем из небольшого примера на C#
[AttributeName(Params)] [AttributeName(Params), AttributeName(Params)] public void Foo() { … }
Немного модифицируем код, чтобы он был валиден для JavaScript
AOP( [AttributeName(Params)], [AttributeName(Params) , AttributeName(Params)], function Foo() { … } );
В принципе, ничего сложного мы не написали, и всё выглядят вполне реализуемым. Осталось реализовать функцию AOP и грамотно организовать расширение логики.
function AOP() { var advices = []; // Разбираем список параметров for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; // Собираем все атрибуты в массивах, пока не встретим функцию, // для которой они все предназначены if (Object.prototype.toString.call( arg ) === "[object Array]") { for (var j = 0, advice = arg[j]; j < arg.length; j++) { advices.push(advice); } } else { if (typeof arg === "function") { name = getFunctionName(arg); // Применяем все атрибуты // т.е. заменяем расширенным вариантом for (var j = 0; j < advices.length; j++) { var advice = advices[j]; arg = advice.apply(arg); } // Добавим функцию в глобальную область видимости, // чтобы сохранить впечатление, что именно там // мы пишем код. Мне это было нужно. Лучше заменить это // атрибутом FunctionName window[name] = arg; } // Очищаем список собранных атрибутов advices.length = 0; } } } function getFunctionName(fn) { var source = fn.toString(); var head = source.split("(")[0]; var fnName = head.split(" ")[1]; return fnName; }
Организуем расширение функциональности
function Advice(implementation) { this.implementation = implementation; } // fn - функция для которой применяем атрибут Advice.prototype.apply = function(fn) { if (typeof fn !== "function") { throw "You can apply an advice only to a function"; } // возвращаем расширенную функцию return this.implementation(fn); }
И добавим необходимые нам атрибуты
function OnBeforeAdvice(onBeforeHandler) { return new Advice(function(fn) { return function() { onBeforeHandler(fn, arguments); return fn.apply(this, arguments); } }); } function OnAfterAdvice(onAfterHandler) { return new Advice(function(fn) { return function() { var result = fn.apply(this, arguments); onAfterHandler(fn, arguments, result); return result; } }); } function OnErrorAdvice(onErrorHandler) { return new Advice(function(fn) { return function() { try { return fn.apply(this, arguments); } catch (e) { onErrorHandler(fn, arguments, e); } } }); } // Атрибут для возможности работы с функциями прототипов // вида "Pet.prototype.getName" function FunctionName(fnName) { return new Advice(function(fn) { var root = window, objs = fnName.split("."), i, oName; for (i = 0; i < objs.length - 1; i++) { oName = objs[i]; if (!root.oName) { root[oName] = {}; } root = root[oName]; } oName = objs[i]; root[oName] = fn; return fn; }); }
Все готово, можно создавать методы и расширять их
<script type="text/javascript"> AOP( [OnBeforeAdvice(onBeforeHandler)], [OnAfterAdvice(onAfterHandler)], function sqr(x) { return x * x; }, [OnAfterAdvice(onBeforeHandler)], [FunctionName("AliasA")], function A() { }, [OnErrorAdvice(onErrorHandler)], function throwsException() { throw "Exception"; } ); function onBeforeHandler(fn, args) { console.log("I know that somebody calls " + fn.toString().split("(")[0]); } function onAfterHandler(fn, args, result) { console.log("Returns " + result + " from " + args[0]); } function onErrorHandler(fn, args, e) { console.log(e + " was thrown"); } onload = function () { sqr(10); A(); AliasA(); throwsException(); } </script>
И теперь можно «элегантно» следить за функциями, помечая их одной строчкой. Можно доработать функцию AOP() и применять атрибуты еще и на объекты, но это будет уже немного иная задача.
В статье на википедии есть ссылки на реализации АОП для JavaScript, можно сравнить. И еще один проект Aspect JS
PS: Сделали справедливое замечание по поводу eval. Вернул кусок кода, который посчитал лишним в данной статье.
