3

Let’s say I have a constructor function which I do not have access to. Into this constructor function I want to inject a self executing init method which gets executed whenever a new instance gets created from this constructor.

For example: let’s say there is a Cat constructor, but I unfortunatly do not have access to it:

function Cat() {
    // ...code which I do not have access to
    // ...maybe it comes from an external file or something?
}

And I can now of course do this to create new cats:

var coolCat = new Cat();

All is well and I have my new cat instance.

But now what I want is (if I actaully had access to the Cat constructor function body, which of course I do not!) something like this:

function Cat() {
  this.roarOnInit = function() {
    alert(’ROOOAAAR!’);
  };
  this.roarOnInit();
}

…so that when I do this:

var coolCat = new Cat();

…I actually get that cool ROAR-alert box!

I do understand how to add the method roarOnInit to the Cat constructor (Cat.prototype.roarOnInit = function …) but is there a way that I easily can add the call to the method (which gets executed whenever a Cat instance is created) to the constructor body?

This seems like such a trivial thing, and it’s probably super easy, but I just can’t seem to figure this out this afternoon! Thanks for bearing with me.

UPDATE

Thanks for the answers so far! I did forget one very important thing, and that is that I will not know in advance what the constructor function will be, or it's name etc. This is because I'm running this through a function which accepts any constructor as a parameter, and eventually returns the constructor (with it's original name/prototype) back.

Kristoffer K
  • 2,053
  • 18
  • 23

2 Answers2

1

Let's start with this definition of Cat:

function Cat(name){
this.name=name;
}
Cat.prototype.meow=function(){alert(this.name)}

Now, what we can do is to overwrite this with a new constructor that returns a regular Cat, but only after running our script:

var oldCat = Cat;
function Cat(name){
    var self=new oldCat(name);
    self.roarOnInit=function(){alert("ROOOOAAARRR")};
    self.roarOnInit();
    return self;
}

We can now do new Cat("Muffin"), and it will roar, and we'll still have access to properties on the original Cat prototype chain. I show this in an example snippet:

// just to be safe, define the original as oldCat()

function oldCat(name){
  this.name=name;
}
oldCat.prototype.meow=function(){alert(this.name)}


//var oldCat = Cat;
function Cat(name){
  var self=new oldCat(name);
  self.roarOnInit=function(){alert("ROOOOAAARRR")};
  self.roarOnInit();
  return self;
}

var coolCat = new Cat("Muffin");
coolCat.meow();

Now, if you want to abstract this to accept any function, it's not too hard. We just have to do a bit of work with the constructor, to pass arguments. Javascript - Create instance with array of arguments

function injectToConstructor(C){
    return function(){
        var self = new (C.bind.apply(C,[C].concat([].slice.call(arguments))))();
        console.log("object initiated:");
        console.log(self);
        return self;
    };
}

Then we can do something like:

Cat = injectToConstructor(Cat);
var coolCat = new Cat("Muffin"); // logs 2 items
coolCat.meow();
Community
  • 1
  • 1
Scimonster
  • 32,893
  • 9
  • 77
  • 89
  • Thank you, I did however forget to mention one vital piece of information and I have now added it to the question! – Kristoffer K Apr 22 '15 at 13:21
  • This alters the original name of the original constructor, and it has to be the same before as well as after the injected init method. I can not alter the name of the constructor when doing instantiation of new instances. Cat needs to work before as well as after the "injection" code. – Kristoffer K Apr 22 '15 at 13:34
  • I just added an update that should take any item and doesn't alter the name. – Scimonster Apr 22 '15 at 13:36
  • Thanks a lot, really appreciate it! The problem though is that I do not know the name of the passed constructor, meaning I can not do the ```Cat = injectToConstructor(Cat)``` since I do not know what the constructor name is. This is hard to explain in a comment, but I've got this function which takes a constructor as a param, called "passedConstructor". If I console.log this I get (with the cat Example) "function Cat() { // etc. }", but if i run the passedConstructor through your function, I get the inner function (of your function) instead when I log it, meaning it breaks. HARD to explain! – Kristoffer K Apr 22 '15 at 14:03
  • Why do you need to preserve the function? – Scimonster Apr 22 '15 at 14:04
  • @KristofferK: If you want to use AOP and injections, your code must not rely on the code of the function, and not on its identity either. And what could `console.log` possibly break? – Bergi Apr 22 '15 at 14:09
  • One reason is that I need the .name property of the passedConstructor. – Kristoffer K Apr 22 '15 at 14:13
  • @Bergi it does not break anything, it just illustrates that for example the .name property gets changed, and that property must not get changed. – Kristoffer K Apr 22 '15 at 14:17
  • 1
    @KristofferK: No code should rely on `.name` properties (it won't work in IE at all for example). If you really need it, you'll have to resort to [arcane magic](http://stackoverflow.com/a/24032179/1048572) – Bergi Apr 22 '15 at 14:31
  • @Bergi I'm using an IE polyfill which does just fine for the browsers I need to support. – Kristoffer K Apr 22 '15 at 15:01
  • @Bergi also: this could (but is not in this case) be on the server. :) – Kristoffer K Apr 22 '15 at 15:07
1

This is because I'm running this through a function which accepts any constructor as a parameter, and eventually returns the constructor (with it's original name/prototype) back.

You cannot really. It's impossible to alter a function's behaviour, no way to "inject" code into it. The only way is to wrap the function, i.e. decorate it, and return a new one.

In your example, it would look like so:

function makeRoarer(constr) {
    function Roar() { // new .name and .length, but that shouldn't matter
        constr.apply(this, arguments);
        this.roarOnInit();
    }
    Roar.prototype = constr.prototype;
    Roar.prototype.constructor = Roar;
    Roar.prototype.roarOnInit = function() {
        alert(’ROOOAAAR!’);
    };
    return Roar;
}

class Cat { … } // whatever
var a = new Cat(); // nothing
Cat = makeRoarer(Cat);
var b = new Cat(); // ROOOAAAR!
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375