javascriptfunctionprototypefunction-prototypesmethod-modifier

Enhance function prototype to call a given function before execution itself


I want to do an AOP-like 'before' functionality for JavaScript functions.

So I looked for existing solution and found the aop-plugin for jQuery. Unfortunately the plugin simply wraps the given function. So any object that points to the original function before enhancing the function, still points to the original function. Calling the function from these objects calls the not enhanced version of the function.

Instead of wrapping functions with other functions, I want to change the function object itself, so previous references do not cause unanticipated behaviour. So I got something like this:

(function(){}).constructor.prototype.before = function(beforeFunction){
    this.beforeFunction = beforeFunction;
};

/* some magic should happen here: enhance call? */

function logFoo() {
    console.log("Foo");
};
function logBar() {
    console.log("Bar");
};

logFoo(); // logs "Foo"

logFoo.before(logBar);

logFoo(); // should log "Bar", then "Foo"

So the question is, how do I get the beforeFunction invoked, when the enhanced function is called?


Solution

  • The body, parameters and referencing environment of a function are immutable so you cannot enhance a function, you can only make new functions.

    You could make your function enhanceable from the beginning like this:

    Function.prototype.enhanceable = function() {
        var original = this,
            enhanced = [],
            ret = function() {
                for( var i = 0; i < enhanced.length; ++i ) {
                    enhanced[i].apply( this, arguments );
                }
    
                return original.apply( this, arguments );
            };
    
        ret.before = function( fn ) {
            enhanced.push( fn );
        };
    
        return ret;
    };
    

    Then:

    var logFoo = function() {
         console.log( "foo" );
    }.enhanceable();
    
    var logBar = function() {
         console.log( "bar" );
    };
    
    logFoo(); //foo
    
    logFoo.before( logBar );
    
    logFoo(); //logs bar, then foo