vsync
vsync

Reputation: 130075

prototype: deep scope of "this" to access instance's scope

How can the the top-most scope can be cached in order to be used deeper in the prototype later, like so:

var Game = function(id){
   this.id = id;
};

Game.prototype = {
  board : {
    init: function(){
       // obviously "this" isn't the instance itself, but will be "board"
       console.log(this.id);
    }
  }
}

var game = new Game('123');
game.board.init(); // should output "123"

update:

Well now that I think about it, I can use apply/call and pass the context...

game.board.init.apply(game);

Upvotes: 6

Views: 410

Answers (5)

Qwertiy
Qwertiy

Reputation: 21380

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 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!

Upvotes: 0

gurvinder372
gurvinder372

Reputation: 68393

Try this way

var Model = function() {this.b = 2;};
Model.prototype.a = function() {
   console.log(this); 
   var that = this;
   this.a.on = function(){
     console.log(that); console.log(m.b);
   };
}

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

You need to do two things

  • Create set the method inside the prototype method, means on should be defined inside a so that it has access to this.

  • Create a copy of the parent reference in that and use it inside on

Upvotes: 0

Stephen
Stephen

Reputation: 5460

There's no such thing as 'deeper in the prototype'. "this" will always be the object that you're calling it on, unless that's changed through being a callback or various ways to rebind. You'll have less sanity loss if you split up your concepts and link them together:

Board = function (game) {
    this.game = game;
}

Board.prototype.init = function () {
    console.log(this.game.id);
}

Game = function () {
    this.id = 123;
    this.board = new Board(game);
}

Game.prototype = {};

Alternatively, if you're hellbent on making it all use the same base, you can do some crazy hack like..

Game = function () {
    this.id = 123;
    var self = this;
    for(var p in this.board) {
        var property = this.board[p];
        if(typeof property == 'function') {
            this.board[p] = function (method) {
                return function () {
                    method.apply(self, arguments);
                }
            }(property)
        }
    }
}

This is a total hack, and it'll make your coworkers hate you. (If you're using the underscore library, there's a bindAll function that helps with this stuff)

Upvotes: 1

Steven Wexler
Steven Wexler

Reputation: 17279

UPDATE

You must make a new instance of for each game if you don't want to provide scope for each function. If you want, you can make board private to Game by making the board constructor a variable in the Game constructor function

var Game = function(id){
    //Keep board private to Game
    var boardClass = function (scope) {
        this.scope = scope;
        this.init = function () {
            if ( this.scope instanceof Game ) {
                console.log(this.scope.id);
            }
        };
    };
    this.id = id;
    //Instantiate a new board for each game
    this.board = new boardClass(this);
};

var game = new Game(123);
//Logs 123
game.board.init();

var game2 = new Game(456);
//Logs 456
game2.board.init()

//Still Logs 123
game.board.init();

Don't use the code below because, as Guffy points out, one board object is shared between all instances of Game. So the solutions below do not work for multiple Games.


You could force game.board.init to make this refer to an instance of game.

var Game = function(id){
    this.id = id;
    this.board.game = this;
};

Game.prototype = {
    board : {
        game: {},
        init: function() {
            //check if this is an instance of board by seeing if it has a game property
            if(this.game) {
                //make this refer to the game if it is referring to the board
                this.init.apply(this.game);
            }
            console.log(this.id);
        }
    }
}

var game = new Game(123);
//Logs 123
game.board.init();

var game2 = new Game(456);
//Logs 456
game2.board.init();

//Logs 456...oops!
game.board.init();

Or you could simply make the instance of game a property on the board.

var Game = function(id){
    this.id = id;
    this.board.scope = this;
};

Game.prototype = {
    board : {
        init: function(){
           // can check if the scope is what we want it to be
           if( this.scope instanceof Game )
                console.log(this.scope.id);
        }
    }
}

var game = new Game(123);
//Logs 123
game.board.init();

var game2 = new Game(456);
//Logs 456
game2.board.init();

//Logs 456...oops!
game.board.init();

Upvotes: 0

Guffa
Guffa

Reputation: 700222

As you only have one instance of the board object, there is no way for it to know what you used to access it. Using game.board or Game.prototype.board to access the object gives exactly the same result.

If you don't want to create one board object for each Game instance, you have to tell the board object which Game object it should consider itself to belong to for each call:

game.board.doSomething(game);

or:

Game.prototype.board.doSomething(game);

Edit:

To create one board for each Game instance, make a constructor for Board, and make the board object aware of the Game instance that it belongs to:

function Game(id) {
  this.id = id;
  this.board = new Board(this);
}

Game.prototype = {
};

function Board(game) {
  this.game = game;
}

Board.prototype = {
  init: function(){
    console.log(this.game.id);
  }
};

var game = new Game('123');
game.board.init(); // outputs "123"

Upvotes: 2

Related Questions