📜 ⬆️ ⬇️

Syntax AOP in JavaScript

It is often useful to add some additional logic to the code that collects data as the application runs, such as counting the number of calls, or handling errors. But you don’t really want to spoil the existing compactly written code (unless of course you have such happiness). The solution in the form of AOP techniques has existed for a long time, and is widely used. On the .NET and Java platforms, many AOP frameworks rely on applying attributes to methods and classes. The code looks almost unchanged, and at the disposal is quite a powerful mechanism for extending functionality.

In JavaScript, such frameworks are not so many, and those that I managed to find when expanding functions were like an ordinary subscription to events. In general, I did not quite like the syntax, I wanted something simple, and more approximate to the “high matters” of .NET and Java.

We set ourselves the typical tasks. Need to add logic:
• before performing the function
• after function execution
• in case of an exception
')
The .NET attributes are closer to me, and the implementation of this syntax suggests itself, so we will start from a small example in C #
[AttributeName(Params)] [AttributeName(Params), AttributeName(Params)] public void Foo() { … } 

We modify the code a bit to make it valid for javascript
 AOP( [AttributeName(Params)], [AttributeName(Params) , AttributeName(Params)], function Foo() { … } ); 

In principle, we did not write anything complicated, and everything looks quite realizable. It remains to implement the AOP function and correctly organize the expansion of logic.
 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; } 


We organize the expansion of functionality
 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); } 


And add the attributes we need
 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; }); } 


Everything is ready, you can create methods and extend them
  <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> 


And now you can "elegantly" follow the functions, marking them with a single line. You can modify the AOP () function and apply attributes to objects as well, but this will be a slightly different task.

The article on Wikipedia has links to AOP implementations for JavaScript, you can compare. And another project Aspect JS

PS: Make a fair comment about eval. Returned a piece of code, which is considered superfluous in this article.

Source: https://habr.com/ru/post/145539/


All Articles