0

I can do something like this:

var foo = ...// some function assignment
var fooString = foo.toString()
...
// add some alert to foo
...
var fooWithAlert = new Function(forStringWithAlert)

Is there a way to mutate first foo instead of creating new function? I need it to monkey patch some dependency without recreating whole hierarchy of objects.

I need to patch a constructor function in a library, just add an alert on every call. But without juggling with prototypes

kharandziuk
  • 12,020
  • 17
  • 63
  • 121
  • you can manipulate the string by adding the alert function and then appending this manipulated string into a head tag, Not sure with other solutions. – Rajshekar Reddy Feb 22 '16 at 10:22
  • It's good to simplify things, but I think you've gone so far simplifying this question that you've probably omitted details relevant to the answers. What, specifically, do you need to do to the function `foo` refers to? Can you come up with a simple, but *representative*, thing you need to do? – T.J. Crowder Feb 22 '16 at 10:25
  • Possible duplicate of [Javascript: Extend a Function](http://stackoverflow.com/questions/4578424/javascript-extend-a-function) – gurvinder372 Feb 22 '16 at 10:26

1 Answers1

3

No, you can't modify the function foo refers to. You can only make foo refer to a new function that does what you want. One way to do that is to use your toString approach, but it's best to avoid that if at all possible, because the function you get as a result will not be the same as the original; the scope it has access to will be different.

Usually, you do want a proxy/wrapper, e.g.:

// The original foo
var foo = function(arg) {
    return "original foo says '" + arg + "'";
};
console.log(foo("bar"));

// Let's wrap it
(function() {
    var originalFoo = foo;
    foo = function() {
        return originalFoo.apply(this, arguments) + " plus updated foo";
    };
})();
console.log(foo("bar"));

This doesn't create a hierarchy of objects or similar, it just wraps foo.

If foo is a constructor function (let's call it Foo), you'll also want to copy Foo.prototype:

// The original Foo
var Foo = function(arg) {
    this.value = "original foo";
    this.arg = arg;
};
Foo.prototype.getArg = function() {
    return this.arg;
};
var f1 = new Foo("bar");
console.log(f1.getArg());

// Let's wrap it
(function() {
    var originalFoo = Foo;
    Foo = function() {
        var rv = originalFoo.apply(this, arguments);
        this.arg += " (plus more from augmented foo)";
        return rv;
    };
    Foo.prototype = originalFoo.prototype;
})();
var f2 = new Foo("bar");
console.log(f2.getArg());

And of course, if you need to wrap a function on Foo.prototype, you can do it just like foo in my first example:

// The original Foo
var Foo = function(arg) {
    this.value = "original foo";
    this.arg = arg;
};
Foo.prototype.getArg = function() {
    return this.arg;
};
var f = new Foo("bar");
console.log(f.getArg());

// Let's wrap its getArg
(function() {
    var originalGetArg = Foo.prototype.getArg;
    Foo.prototype.getArg = function() {
        return originalGetArg.apply(this, arguments) + " updated";
    };
})();
console.log(f.getArg());

Note how it doesn't matter that we wrapped the prototype function after creating the f object.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • better perhaps to encapsulate the `originalFoo` in an IIFE ? – Alnitak Feb 22 '16 at 10:24
  • what if he wants to add some 20 lines of code logic into it? Can it still be achieved by this method? – Rajshekar Reddy Feb 22 '16 at 10:24
  • I guess i need to be more concrete https://github.com/Jxck/assert/blob/master/assert.js#L190 What happens with `foo` prototype in the case? – kharandziuk Feb 22 '16 at 10:24
  • 1
    @Reddy: Yes, the quantity doesn't matter, just the location. Wrappers work for doing things before the original, or after, or both, but not *in the middle*. – T.J. Crowder Feb 22 '16 at 10:27
  • 2
    @Alnitak: I wasn't expecting the OP to use the code verbatim, I was expecting some common sense. :-) – T.J. Crowder Feb 22 '16 at 10:28
  • @kharandziuk: The details related to your question must be **in** your question, not just linked. – T.J. Crowder Feb 22 '16 at 10:28
  • @kharandziuk: I've updated the answer to include a note about constructor functions, and functions on a constructor's `prototype` object. – T.J. Crowder Feb 22 '16 at 10:37
  • one minor gotcha (discovered whilst playing with a similar system) is that the replaced function won't have the same arity as the original. I don't know if it's allowable to just say `Foo.length = originalFoo.length` – Alnitak Feb 22 '16 at 10:48
  • @Alnitak: *"the replaced function won't have the same arity as the original"* Good point! *"I don't know if it's allowable to just say `Foo.length = originalFoo.length`"* No, `length` on functions is read-only [according to the spec](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-function-instances-length). It's rare, of course, for anything to actually check the arity of the function, but it's a good point nonetheless. If I needed it, I'd probably just have branch and create the arity I needed (up to, say, 10 and using `new Function` beyond that)... – T.J. Crowder Feb 22 '16 at 10:54
  • Yeah, I tried it and discovered that `f.length` is indeed read-only. I have seen code recently that checks the arity of a passed function, but I don't recall what it was. – Alnitak Feb 22 '16 at 10:56