user578895
user578895

Reputation:

Getting a 'this' reference to a 2nd level prototype function

I'm fairly certain this isn't possible, but wanted to see if anyone had some ingenious ideas as to how to make it possible.

I want the following code to work:

var x = new foo();
x.a.getThis() === x; // true

In other words, I want x.a.getThis to have a reference to this being x in this case. Make sense?

In order to get this to work one level deep is simple:

function foo(){}
foo.prototype.getThis = function(){ return this; }
var x = new foo();
x.getThis() === x; // true

One thing, I want this to work as a prototype, no "cheating" by manually binding to this:

function foo(){
    this.a = {
        getThis : (function(){ return this; }).bind(this)
    };
}

Although the above is a perfect functional example of what I'm trying to achieve, I just don't want all the extra functions for each instance :)

FYI, the actual use case here is that I'm creating classes to represent Cassandra objects in node and I want to be able to reference a super-column --> column-family --> column via foo.a.b and keep a reference to foo in the deep function.

Upvotes: 3

Views: 228

Answers (3)

user578895
user578895

Reputation:

Answering my own question because someone else may find it useful. Not sure if I'll end up going with this or Squeegy's solution. The functions are only ever defined once and then the containing object is cloned and has parent = this injected into it:

function foo(){
    var self = this, nest = this.__nestedObjects__ || [];

    nest.forEach(function(prop){
        self[prop] = extend({ parent : self }, self[prop]);
    });
}

// bound like this so that they're immutable
Object.defineProperties(foo.prototype, {
    bar : {
        enumerable : true,
        value : {
            foobar : function(){
                return this.parent;
            },
            foo : function(){},
            bar : function(){}
        }
    },
    __nestedObjects__ : { value : ['bar'] }
});

var fooInst = new foo();
console.log(fooInst.bar.foobar() == fooInst);

or based on Squeegy's solution:

function foo(){
    for(var cls in this.__inherit__){
        if(!this.__inherit__.hasOwnProperty(cls)){ continue; }

        this[cls] = new (this.__inherit__[cls])(this);
    }
}

var clsA;
// bound like this so that they're immutable
Object.defineProperties(foo.prototype, {
    __inherit__ : { value : {
        bar : clsA = function(parent){
                Object.defineProperty(this, '__parent__', { value : parent });
            }
        }
    }
});

clsA.prototype = {
    foobar : function(){
        return this.__parent__;
    }
};

var fooInst = new foo();
console.log(fooInst.bar.foobar() == fooInst);

Upvotes: 0

Alex Wayne
Alex Wayne

Reputation: 187272

You can't do this without a forced bind of some kind. You say you don't want to "cheat" but this breaks the standard rules about what this is, so you have to cheat. But JS lets you cheat, so it's all good.

BTW, for what it's worth coffee script makes this so trivial.

foo = ->
  @a = getThis: => this

The fat arrow => preserves the context of this for from the scope it was called in. This allows you to easily forward the context to another level.

That code gets compiled to this JS:

var foo;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
foo = function() {
  return this.a = {
    getThis: __bind(function() {
      return this;
    }, this)
  };
};

Which basically just does what you say you do not want to do.


Or if the value doesn't have to this specifically, you can set the "owner" in the child object.

var A = function(owner) {
  this.owner = owner;
};
A.prototype.getThis = function() {
  return this.owner;
};
var Foo = function() {
  this.a = new A(this);
};

var foo = new Foo();

if (foo.a.getThis() === foo) {
    alert('Happy dance');
} else {
    window.location = 'https://commons.lbl.gov/download/attachments/73468687/sadpanda.png';
}

http://jsfiddle.net/4GQPa/

And the coffee script version of that because I am a passionate and unreasonable zealot for it:

class A
  constructor: (@owner) ->
  getThis: -> @owner

class Foo
  constructor: -> @a = new A(this)

foo = new Foo()
if foo.a.getThis() is foo
  alert 'Happy Dance'
else
  window.location = 'https://commons.lbl.gov/download/attachments/73468687/sadpanda.png'

Upvotes: 2

RobG
RobG

Reputation: 147533

Impossible to do reliably without binding the value at the start since the value of a function's this is set by the call. You can't know beforehand how it will be called, or which functions need a special or restricted call to "preserve" the this -> this relationship.

The function or caller's this may be any object, there may not be a this -> this at all. Consider:

var x = {
  a : {
    b: function() {return this;}
  }
}

When you call x.a.b(), then b's this is a. But if you do:

var c = x.a.b;
c(); // *this* is the global object

or

x.a.b.call(someOtherObject);

What is the value of this -> this in these cases?

Upvotes: 0

Related Questions