125

I'm attempting to customize an existing JS library without modifying the original JS code. This code loads in a few external JS files which I do have access to, and what I'd like to do is change one of the functions contained in the original file without copying and pasting the whole thing into the second JS file.
So for example, the off limits JS might have a function like this:

var someFunction = function(){
    alert("done");
}

I'd like to be able to somehow append or prepend some JS code into that function. The reason is primarily that in the original untouchable JS the function is pretty enormous and if that JS ever gets updated, the function I overwrite it with will be out of date.

I'm not entirely sure this is possible, but I figured I'd check.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Munzilla
  • 3,805
  • 5
  • 31
  • 35

6 Answers6

236

If someFunction is globally available, then you can cache the function, create your own, and have yours call it.

So if this is the original...

someFunction = function() {
    alert("done");
}

You'd do this...

someFunction = (function() {
    var cached_function = someFunction;

    return function() {
        // your code

        var result = cached_function.apply(this, arguments); // use .apply() to call it

        // more of your code

        return result;
    };
})();

Here's the fiddle


Notice that I use .apply to call the cached function. This lets me retain the expected value of this, and pass whatever arguments were passed in as individual arguments irrespective of how many there were.

  • 12
    This answer should really be the top one -- it preserves the functionality of the function in question... +1 from me. – Reid Feb 03 '12 at 22:10
  • 20
    +1 for using `apply`: this is the only answer that *really* solves the problem. – lonesomeday Feb 03 '12 at 22:11
  • 2
    @minitech: What... You're not familiar with JavaScript's `funciton` keyword? ;) Thanks for the edit –  Mar 10 '12 at 15:26
  • @amnotiam. In other thread I saw this kind of answer: `return cached_function.apply(this, Array.prototype.slice.call(arguments));` What are the differences? – gdoron Mar 22 '12 at 20:37
  • @gdoron: The difference is that the `.slice()` call is unnecessary... that is unless you want a subset of the `arguments`, in which case you'd pass an argument to slice, like `...slice.call(arguments, 1)` which would exclude the first argument. But if you're passing on all the arguments, there's no need to convert to an Array. –  Mar 22 '12 at 21:42
  • @amnotiam. The guy who used it has just answered why he uses it. [here](http://stackoverflow.com/posts/comments/12525222) What do you say about it? – gdoron Mar 22 '12 at 21:44
  • 1
    @gdoron: I just added a comment. Unless I'm just really confused right now, I don't know of any browser that won't accept an actual Arguments object as the second arg for `.apply()`. But if it was an Array-like object like a jQuery object for example, then yes, some browsers would throw an error –  Mar 22 '12 at 21:55
  • This appears to work on functions defined like; `function someFunction(){}` as well unless I'm mistaken? – Joel Jan 23 '13 at 12:16
  • Hmm, this is an interesting answer, but it seems that this only works when the function in question is globally available. I've tried this solution several times without a global scope and it doesn't seem to work at all and I get errors that my function is undefined. Does anyone know a way around that? – GDP2 Apr 20 '15 at 15:48
  • @GDP2: This will work just fine with functions in any scope. If you can access the function to call it, you can access it to override it as shown above. You'll need to show some code if you're having issues with it. –  Apr 20 '15 at 16:15
  • @squint Well, here I've created an example so you can see what I mean about it not working: https://jsfiddle.net/Ylluminarious/rc8smp1z/ – GDP2 Apr 20 '15 at 16:22
  • @GDP2: Your function is a property of the `this` object, but then you're trying to access it as a variable. I updated your demo to use `this.someFunction` and then to invoke the IIFE using `.call(this)`, so that it sets the `this` value of that IIFE. You also had a typo at the bottom. https://jsfiddle.net/rc8smp1z/1/ –  Apr 20 '15 at 19:36
  • @squint Yes, I ended up noticing that and I fixed the problem. Thank you for the help, though. – GDP2 Apr 20 '15 at 22:17
  • 2
    This answer, as it happens, solves the problem of using instanced objects to control individual YouTube videos embedded via the YouTube Iframe API. You have no idea how long I've been trying to work around the fact that the API requires it's callback to be a single global function when I'm trying to create a class to handle each video's data in a unique modular way. You are my hero for the day. – Carnix Mar 17 '17 at 22:15
34

first store the actual function in a variable..

var oldFunction = someFunction;

then define your own:

someFunction = function(){
  // do something before
  oldFunction();
  // do something after
};
driangle
  • 11,601
  • 5
  • 47
  • 54
  • 9
    If your function is a method you will need to use `apply` to call it. See am's answer. – hugomg Feb 04 '12 at 18:03
  • It worked for me, but needed to replace `someFunction` with `window.someFunction` in the above code. The reason be it that my function was declared inside a jquery `$(document).ready()` handler. – Nelson Sep 27 '19 at 01:51
10

You can make a function that calls your code, and then calls the function.

var old_someFunction = someFunction;
someFunction = function(){
    alert('Hello');
    old_someFunction();
    alert('Goodbye');
}
gen_Eric
  • 223,194
  • 41
  • 299
  • 337
6

Also. If you want to change local context you have to recreate function. For example:

var t = function() {
    var a = 1;
};

var z = function() {
    console.log(a);
};

Now

z() // => log: undefined

Then

var ts = t.toString(),
    zs = z.toString();

ts = ts.slice(ts.indexOf("{") + 1, ts.lastIndexOf("}"));
zs = zs.slice(zs.indexOf("{") + 1, zs.lastIndexOf("}"));

var z = new Function(ts + "\n" + zs);

And

z() // => log: 1

But this is just the simplest example. It will take still a lot of work to handle arguments, comments and return value. In addition, there are still many pitfalls.
toString | slice | indexOf | lastIndexOf | new Function

Ivan Black
  • 4,827
  • 1
  • 35
  • 33
6

I don't know if you can update the function, but depending on how it is referenced, you can make a new function in its place:

var the_old_function = someFunction;
someFunction = function () {
    /* ..My new code... */
    the_old_function();
    /* ..More of my new code.. */
}
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
2

The proxy pattern (as used by user1106925) can be put inside a function. The one I wrote below works on functions that aren't in the global scope, and even on prototypes. You would use it like this:

extender(
  objectContainingFunction,
  nameOfFunctionToExtend,
  parameterlessFunctionOfCodeToPrepend,
  parameterlessFunctionOfCodeToAppend
)

In the snippet below, you can see me using the function to extend test.prototype.doIt().

// allows you to prepend or append code to an existing function
function extender (container, funcName, prepend, append) {

    (function() {

        let proxied = container[funcName];

        container[funcName] = function() {
            if (prepend) prepend.apply( this );
            let result = proxied.apply( this, arguments );
            if (append) append.apply( this );
            return result;
        };

    })();

}

// class we're going to want to test our extender on
class test {
    constructor() {
        this.x = 'instance val';
    }
    doIt (message) {
        console.log(`logged: ${message}`);
        return `returned: ${message}`;
    }
}

// extends test.prototype.doIt()
// (you could also just extend the instance below if desired)
extender(
    test.prototype, 
    'doIt', 
    function () { console.log(`prepended: ${this.x}`) },
    function () { console.log(`appended: ${this.x}`) }
);

// See if the prepended and appended code runs
let tval = new test().doIt('log this');
console.log(tval);
pwilcox
  • 5,542
  • 1
  • 19
  • 31