6

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);
6
  • Its like a static class, when you make object in prototype, its same object in all instances.. its means (new Game()).board == Game.board and (new Game()).board.init == Game.board.init .. and it means, 'this' of object inner another object is parent object, its means 'this' in inner init function is same with board: in init function -> this == Game.board Commented May 11, 2013 at 23:13
  • Will you need multiple instances of Game? Looks like it should be a namespace (in the form of an object literal); then you'd have constructors and prototypes for Board, and whatever else you need. Commented May 11, 2013 at 23:19
  • yes I must have instances. unknown number of them. Commented May 11, 2013 at 23:19
  • I've decided to use 'Apply' after all Commented May 11, 2013 at 23:38
  • @vsync: If you use apply, you will be calling the init method as a member of the game instance, not as a member of the board instance. Then it would be better to just put the members of the board object in the Game object to start with, and ditch the board object completely. Commented May 12, 2013 at 12:45

5 Answers 5

2

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"
Sign up to request clarification or add additional context in comments.

7 Comments

board has things inside it that interacts with each specific instance (since every game has different board), so calling it directly won't be a good idea.
@vsync You added board to Game.prototype, so all Game instances share the same board. Maybe that's not what you actually want, considering your comment above.
@vsync: If you want each Game instance to have their own board, you have to change your implementation so that you create one board for each Game instance. Then the board can have an owner, and can be made aware of which it is. See the code that I added above.
I've designed it exactly the way I want it, with shared prototype to all instances. I've being coding JS for 8 years, i'm not new to this..but the thing is, I was wandering perhaps there was a way to save the scope with some closures trickery..sometimes JS requires more creativity of the mind
steaks's answer was the thing I was looking for, a JS trickery to make the innermost prototyped objects know about the uppermost scope. neat.
|
1

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)

4 Comments

yeah obviously...but I don't want to change my code, which is written and split everything to be "flat" functions without nesting in namespace which makes sense, this is ugly programing...
also I have a ton of inner functions and it would be a mess writing Game.prototype.something= function(){ .. } all the time, I just think it is not worth the easy fix I gain for the core scope access
The hack does not work as it misses a closure for property. It should just use ES5 bind: this.board[p] = this.board[p].bind(this);
You're right in that it was missing a closure. However, it shouldn't "just use" bind - IE8 doesn't support it, and that's still a 'popular' browser.
0

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();

5 Comments

You are a good developer my friend, I love this second part. I've re-written it to better match
That only works for one single instance of the Game object.
@Guffa you are right. Developers should not use this answer as is.
@Guffa I think my revised solution should work for multiple instances of Game.
Neither of these is a good solution. 1) because of the unnecessary "class" (with extra prototype object, while only one "instance" is needed) 2) because it overwrites the properties on that one shared board object 3) because it has the same problem as 2
0

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

Comments

0

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.