2

Suppose I have following code:

var Model = function() {};
Model.prototype.a = function() {//do smth
Model.prototype.a.on = function() {//do smth

var m = new Model();
m.a();
m.a.on();

Now I need reference to specific object m from m.a() and m.a.on() calls. When calling m.a(), i have this referring to m.

Is it possible to get reference to m from m.a.on() call somehow?

Vecnas
  • 1,387
  • 1
  • 10
  • 13

3 Answers3

3

It's a very bad idea to do so, as it leads to very strange behaviour in some cases, but it's possible:

var Model = function(x) { this.x = x };

Object.defineProperty(Model.prototype, 'a', (function() {
  var lastSelf;
  function get() { return lastSelf.x }
  get.on = function () { return lastSelf.x * 2 };
  return { get() { lastSelf=this; return get } };
})());

var m = new Model(17);
console.log(m.a(), m.a.on());

Why? I see your answer below, trying to realize what are bad cases.

You can't pass a through the variable.
You must grant access to on immediately after getting property a of the same object:

var Model = function(x) { this.x = x };

Object.defineProperty(Model.prototype, 'a', (function() {
  var lastSelf;
  function get() { return lastSelf.x }
  get.on = function () { return lastSelf.x * 2 };
  return { get() { lastSelf=this; return get } };
})());

var m1 = new Model(1), m2 = new Model(3);
console.log(m1.a(), m2.a(), m1.a.on(), m2.a.on()); // 1 3 2 6 - ok
var a1 = m1.a, a2 = m2.a;
console.log(m1.a(), m2.a(), a1.on(), a2.on()); // 1 3 6 6 - ooops!
console.log(m1.a(), m2.a(), m1.a(), a1.on(), a2.on()); // 1 3 1 2 2 - ooops!

And the other solution, but with using __proto__. According to ES6 this solution is valid for browser enviroments and for server enviroments __proto__ have to be replaced by Object.setPrototypeOf. Be sure to check browser support and other warnings.

This solution adds 1 function and 1 object per each instance.

function Model(x) { 
  this.x = x;

  this.a = function () { return Model.prototype.a.call(this, arguments) };
  this.a.__proto__ = Object.create(Model.prototype.a);
  this.a.this = this;
}

Model.prototype.a = function () { return this.x };
Model.prototype.a.on = function () { return this.this.x * 2 };

var m1 = new Model(1), m2 = new Model(3);
console.log([m1.a(), m2.a(), m1.a.on(), m2.a.on()] == "1,3,2,6");
var a1 = m1.a, a2 = m2.a;
console.log([m1.a(), m2.a(), a1.on(), a2.on()] == "1,3,2,6");
console.log([m1.a(), m2.a(), m1.a(), a1.on(), a2.on()] == "1,3,1,2,6");
Qwertiy
  • 19,681
  • 15
  • 61
  • 128
1

You can rebind the 'grandchild' methods manually in the constructor:

bindAll = function(self, obj) {
    Object.keys(obj).forEach(function(k) {
        if(typeof obj[k] === 'function')
            obj[k] = obj[k].bind(self);
    });
}

var Model = function() {
    bindAll(this, this.a);
    this.x = 123;
};

Model.prototype.a = function() {}
Model.prototype.a.on = function() {
    console.log(this.x);
}

var m = new Model();
m.a();
m.a.on();

A more memory-savvy way is to use an explicit pointer to the root class and consistently use this.root instead of just this in methods:

var Model = function(x) {
    this.x = x;
    this.model = this;
    this.a = Object.create(this.a);
    this.a.model = this;
};

Model.prototype.a = function() {
    console.log(this.model.x);
}
Model.prototype.a.on = function() {
    console.log(this.model.x);
};

var m1 = new Model(11), m2 = new Model(22);
m1.a.on();
m2.a.on();
m1.a.on();
georg
  • 211,518
  • 52
  • 313
  • 390
  • What if I create about 10,000 objects? Initially I didn't use prototypes, re-written to improve performance for such cases. – Vecnas Mar 16 '16 at 09:46
  • I'll check this,looks simple ) – Vecnas Mar 16 '16 at 09:50
  • If you create 10,000 objects, this code will create 10,000 x number_of_methods closures ;( If you're not happy with the default `this` you have to store your `this` somewhere. – georg Mar 16 '16 at 09:51
  • @Vecnas: added a more efficient solution. – georg Mar 16 '16 at 09:54
  • thank you, i'll check it and report ) – Vecnas Mar 16 '16 at 09:55
  • Your second solution is wrong. Just create 2 objects. – Qwertiy Mar 16 '16 at 09:56
  • @Qwertiy: not sure what you mean by this. Care to elaborate? – georg Mar 16 '16 at 09:58
  • yes, second is wrong, it refers to same object, just checked it, behaves like static variable – Vecnas Mar 16 '16 at 10:01
  • Initially I think it's impossible to keep tracking of this, because .a defined via prototype, so it looks like static – Vecnas Mar 16 '16 at 10:03
  • `var Model = function(x) { this.x = x; this.model = this.a.model = this; }; Model.prototype.a = function() { console.log(this.model.x); }; Model.prototype.a.on = function() { console.log(this.model.x); }; var m1 = new Model(1), m2 = new Model(2); m1.a(), m1.a.on(), m2.a(), m2.a.on();` The result is `1 2 2 2` when `1 1 2 2` is expected. – Qwertiy Mar 16 '16 at 10:07
  • @Vecnas, yes it's so. – Qwertiy Mar 16 '16 at 10:08
  • @Qwertiy: I see now, thanks, fixed. – georg Mar 16 '16 at 10:10
  • Unfortunately we loosing all prototype gain due to creation of .a separately for every object. That's what I trying to get rid of ( – Vecnas Mar 16 '16 at 10:11
  • @Vecnas, no, we don't. Or I don't undestand what you are talking about. There is no deep cloning, just a new object. – Qwertiy Mar 16 '16 at 10:12
  • @Vecnas: yes, but the cloned objects will be rather tiny as they contain only `proto` and `model`. I think it's a reasonable price. – georg Mar 16 '16 at 10:14
  • I did simple test, creating 10k objects with 2 functions, one variant - functions were added per object, second - set via prototypes. Very different performance. I'll check performance of proposed solution too, not sure about results – Vecnas Mar 16 '16 at 10:15
  • @Vecnas: actually, the 2nd version still doesn't work if you want `a` to be a function (`m1.a()` won't work). ;( – georg Mar 16 '16 at 10:21
  • Just put you object into function's `__proto__`. – Qwertiy Mar 16 '16 at 10:27
  • @Qwertiy: I tried, but couldn't find a solution without wrapping `a` in a closure. Care to share your approach? – georg Mar 16 '16 at 15:25
  • Updated my answer. It was a bit more compicated than I expected. – Qwertiy Mar 16 '16 at 15:44
0

You can't access directly the parent object from a.on. You have to define some property (e.g. parent) linked to main object before calling a.on:

var Model = function() {};
Model.prototype.a = function() {//do smth
  console.log(this.i);
}
Model.prototype.a.on = function() {//do smth
  console.log(this.parent.i);
}

var m = new Model();
m.i = 11;
m.a();
m.a.parent = m
m.a.on();
Boroda
  • 21
  • 2
  • Assigning parent manually every time you need to call the method is a very bad solution. By the way you can use `call` instead: `m.a.on.call(m)`. – Qwertiy Mar 16 '16 at 10:14