2

In JavaScript, if I attach a function to an object's prototype, within that function, this refers to the current instance of the object. For example:

function RageFest(inquiry) {
    this.inquiry = inquiry;
}

RageFest.prototype.myFunction0 = function () {
    console.log(this.inquiry);
};

new RageFest("you mad, bro?").myFunction0(); // prints "you mad, bro?"

Running this in Node prints you mad, bro? to the terminal, as expected.

Now, here's where I run into trouble: suppose I want to introduce a second level of organization (maybe because RageFest has lots of methods attached to it, and I want to organize them).

RageFest.prototype.myObject = {
    myFunction1: function () { console.log(this.inquiry); },
    myFunction2: function () { console.log(this.inquiry); }.bind(this),
    myFunction3: function () { console.log(this.inquiry); }.bind(RageFest)
};

var ragefest = new RageFest("you mad, bro?");

ragefest.myObject.myFunction1(); // undefined
ragefest.myObject.myFunction2(); // undefined
ragefest.myObject.myFunction3(); // undefined

None of these work! If I make myObject's three functions log this (rather than this.inquiry), it shows that:

  • myFunction1: this refers to myObject
  • myFunction2: this refers to the global object
  • myFunction3: this refers to the RageFest constructor

However, binding the instance (rather than the constructor) works:

RageFest.prototype.myObject.myFunction4 = function () {
    console.log(this.inquiry);
}.bind(ragefest);

ragefest.myObject.myFunction4(); // prints "you mad, bro?"

This prints out you mad, bro?, as desired, but it feels like a terrible hack. It also requires me to create an instance of RageFest beforehand, which is annoying.

So, my question is, is there a way of achieving the effect I want -- functions inside a nested object that have access to the current instance using the this keyword -- without resorting to this hackery?

tinybike
  • 562
  • 2
  • 13
  • The only way I can think of doing it is also sort of hacking, and wouldn't be using `this`, it would be saying `var that = this;` outside of the functions and using `that` as the variable to access the instance instead. I'm not going to post this as an answer because I'm not sure if this is acceptable to you or not, and I'm not sure if there is or isn't a better way to use bind(). – Pandacoder Jul 11 '15 at 01:18
  • 1
    @tinybike will `this.inquiry` always be the same value? Or is it a value that might potentially change? – kevin628 Jul 11 '15 at 01:24
  • @kevin628 in the real-world version of this problem, it can be different for different instances -- so, really, `RageFest` should take a parameter. Just updated the question so that it does! – tinybike Jul 11 '15 at 01:28
  • Function.prototype.call might be the way to go, since with Function.prototype.bind you will have to bind different instances of your object. But then you can't use described above way of accessing functions . – halfzebra Jul 11 '15 at 01:34
  • 2
    So would a closure be helpful, then? Pop open your browser's console and take a look here: https://jsfiddle.net/bcwtr0ka/1/ – kevin628 Jul 11 '15 at 01:34
  • @kevin628 Interesting idea! Edited https://jsfiddle.net/bcwtr0ka/1/ -- uses bind in a much less hacky way. – tinybike Jul 11 '15 at 01:54
  • @tinybike the only downside is that you're creating a potential memory leak by defining the same function over and over for every instance. – halfzebra Jul 11 '15 at 01:57
  • I wouldn't classify it as a memory leak, but perhaps an inefficient use of memory... and that may not ultimately be a problem for tinybike's needs. A unique set of functions is created every time RageFest is new'd up, but as soon as any instance goes out of scope, the closure is marked for collection and reclamation by the garbage collector. An alternative solution is to use a service locator pattern to pluck the string from an external dependency. In the end, I recommend keeping `this` clean and untouched. Much easier to test and maintain over time. – kevin628 Jul 11 '15 at 02:05
  • Oops, just realized I didn't save the fiddle -- here's the actual updated one: https://jsfiddle.net/bcwtr0ka/2/ – tinybike Jul 11 '15 at 02:13

1 Answers1

3

Instead of creating nested object creating getter will be helpful, which will be called in constructor function and assigned to an object property.

function RageFest(inquiry) {
    this.inquiry = inquiry;
    this.myObject = this.getMyObject();
}

RageFest.prototype.myFunction0 = function () {
    console.log(this.inquiry);
};

RageFest.prototype.getMyObject = function(){
    var that = this;
    return {
      myFunction1: function () { console.log(that.inquiry); },
      myFunction2: function () { console.log(that.inquiry); } 
    }
};

var ragefest = new RageFest("you mad, bro?");
ragefest.myObject.myFunction1();
ragefest.myObject.myFunction2();
abs
  • 801
  • 6
  • 15
  • 1
    Nice! I ended up refactoring my code a bit and solving this using [dependency injection](https://gist.github.com/tinybike/2cb7451430d9ef82e7d3), which was a slightly better fit for my problem. Your answer is a good one for the question I asked, though, so I accepted it. – tinybike Jul 21 '15 at 01:42