Зачастую бывает полезно добавить какую-нибудь дополнительную логику в код, которая собирает данные по ходу работы приложения, например подсчет количества вызовов, или обработка ошибок. Но портить существующий компактно написанный код (если конечно у вас есть такое счастье) не очень-то хочется. Решение в виде приемов АОП существует уже давно, и широко применятся. На платформах .NET и Java многие АОП фреймворки ориентируются на применение атрибутов к методам и классам. Выглядит код почти неизмененным, а в распоряжении оказывается достаточно мощный механизм расширения функциональности.
В 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);
}
// Добавим функцию в глобальную область видимости
eval("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);
}
}
});
}
Все готово, можно создавать методы и расширять их
<script type="text/javascript">
AOP(
[OnBeforeAdvice(onBeforeHandler)],
[OnAfterAdvice(onAfterHandler)],
function sqr(x) {
return x * x;
},
[OnAfterAdvice(onBeforeHandler)],
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();
throwsException();
}
</script>
И теперь можно «элегантно» следить за функциями, помечая их одной строчкой. Можно доработать функцию AOP() и применять атрибуты еще и на объекты, но это будет уже немного иная задача.
В статье на википедии есть ссылки на реализации АОП для JavaScript, можно сравнить. И еще один проект Aspect JS
Автор: pifarik