2

I wanted to have a method of adding functionality to pre-existing functions, so I made this:

Function.prototype.attachFunction = function(toAttach)
{
    var func = this; //This is the original function, which I want to attack toAttach function to.

    //Can I do this without wrapping it in a self-executing function? What's the difference?
    func = (function()
    {
        return function()
        {
            func();
            toAttach();
        }
    })();

    return func; //Return the newly made function, not really important.    
}

I paste this into the Google Chrome console, and there are no errors, however, it does not (or so it would seem) alter the original function at all.

f = function() {console.log("g");};
f.attachFunction(function(){console.log("New function!");});
f(); //Prints out just "g".
corazza
  • 31,222
  • 37
  • 115
  • 186

6 Answers6

1

The attachFunction method you've set on the Function prototype is a higher-order function, or combinator, that accepts a function and returns a new function. It is inherited by any function created thereafter, as expected. However, when it is called it does not alter the state of f in any way, it simply produces a new function that calls f, or the calling function rather.

So, if you wanted to see both console.log messages, you simple need to call the function it returns, like so:

f.attachFunction(function(){console.log("hi")})();
or more simply:
f.attachFunction(f)();

Note that although functions are objects, the execution context (code) of f is not a property of f such that it can be manipulated directly, it is kept in an internal attribute.

1

When attachFunction executes, it returns a function which executes func() and toAttach. However, if you change your code to be the following, it will attempt to execute both functions, where currently the old f is still called at the end.

f = function() {console.log("g");};
f = f.attachFunction(function(){console.log("New function!");});
f(); //Causes an infinite loop since calling func() is the same as calling this()

To merge two functions we need to wrap them the same way but not while extending Function

var func1 = function(){
    console.log('g');
};
var func2 = function(){
    console.log('New function!');
};

func1 = (function(f1,f2){
    return function(){            
        f1();
        f2();
    }
}(func1, func2));

func1(); //Writes out both g and New function!​​​​​​​​​​​​​​​​​​​​

The reason I pass in func1, and func2 as parameters is to prevent the same problem of getting into an infinite loop. I want to maintain a reference to these functions at that point in time.

A helper function would then be the following

function combineFunctions(f1, f2){
    return function(){
        f1();
        f2();
    }
}

and would be used like this

var f = function() {console.log("g");};
f = combineFunctions(f,function(){console.log("New function!");});
f();
  • Yes, this works, but I still don't know why my method doesn't. – corazza Jul 14 '12 at 17:55
  • You set `var func = this;` but then immediately set `func` equal to a new function. This new function calls `func()` and `toAttach()`. But you have redefined `func` to be the new function, not the calling function. The calling function is never overwritten, nor can it be. If instead of creating a `func` variable, you assigned directly to `this` you would get a "Invalid left-hand side in assignment". – Chris Winsor Jul 14 '12 at 19:43
0

Underscore.js' wrap() function should be quite close to what you want.

Haroldo_OK
  • 6,612
  • 3
  • 43
  • 80
0

Currently, you're trying to call a variable as if it were a function (toAttach is a variable and you're smacking some parenthesis on the far side of it). Consider this: toAttach may CONTAIN a function, but that does not make it a function (much like a grocery store is not an apple). Instead you need to call the function contained within the variable.

There are several ways to do this (although only one or two apply here), none of the [right] ways involve eval() (life lessons).

Consider this thread. Pass the function name as a string and then use

window[toAttach]();

to call the function. It's a bit hacky, and lacks the finesse of passing the function as an object, but it works.

Alternatively (completely depending on your needs) you can bind the function to an event. If you're extra sneaky (and why not?) you can use

setTimeout(toAttach, 0);

Where toAttach is a reference to the actual function (again).

Finally, and best, I believe, is the Function.call() method. Using this, you can call a function stuffed in a variable, and even pass it a context for this.

toAttach.call([Context]);

Where [Context] is the context of the function (ie, what this refers to when inside of the function, in most cases it can be left blank to get the desired scope). I can't say I've tested it under your certain circumstances (thus why I have included it last), but I believe it should give you the results you're looking for.

Community
  • 1
  • 1
Sandy Gifford
  • 7,219
  • 3
  • 35
  • 65
  • I don't understand what you are talking about! Functions *are* objects, and I can pass them around and call them just fine. Your methods don't work, either. :( – corazza Jul 14 '12 at 17:48
0

Here's my attempt to achieve this, it is not clean as if it was entirely in the prototype but it works. There are probably better ways than mine.

var bindings={};

function execBindings(func_name) {
    if (bindings[func_name] != undefined) {
        for (var i=0;i<bindings[func_name].length;i++) {
            bindings[func_name][i]();
        }
    }
}

Function.prototype.bind=function(action){
    if (bindings[this.name] == undefined) {
        bindings[this.name]=[];
    }
    bindings[this.name].push(action);
}

Then, to use the binding :

function f() {
    alert("foo");
    execBindings("f");
}

f.bind(function(){
    alert("bar");
});

f.bind(function(){
    alert("test");
});

f();

which results in 3 alerts, in the order thwy were binded to the function.

Frederik.L
  • 5,522
  • 2
  • 29
  • 41
0

There are two problems I just found.

problem 1.

f = function() {console.log("g");};
f.attachFunction(function(){console.log("New function!");});
f();

It definitely doesn't work, cause attachFunction just wrap around your original function and return the wrapped, it doesn't change the original function, which means your f function is still f = function() {console.log("g");}

Problem 2.

If you reassign f with the result of attachFunction, an Maximum call stack size exceeded will occur. In the definition of attachFunction:

func = (function()
{
    return function()
    {
        func();
        toAttach();
    }
})();

return func; 

The func function call himself recursively.

Update

I prefer this approach.

Function.prototype.attachFunction = function(toAttach)
{
    var that = this; 

    func = (function()
    {
        return function()
        {
            that();
            toAttach();
        }
    })();

    return func; //Return the newly made function, not really important.    
}
f = function() {console.log("g");};
f = f.attachFunction(function(){console.log("New function!");});
f();
xiaowl
  • 5,177
  • 3
  • 27
  • 28
  • OK, I know that, but, is there a way to actually alter the original function, without relying on return? – corazza Jul 15 '12 at 12:54