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);
vsync
  • 118,978
  • 58
  • 307
  • 400
  • 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** – Mehdi Yeganeh May 11 '13 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. – bfavaretto May 11 '13 at 23:19
  • yes I must have instances. unknown number of them. – vsync May 11 '13 at 23:19
  • I've decided to use 'Apply' after all – vsync May 11 '13 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. – Guffa May 12 '13 at 12:45
  • possible duplicate of [Organize prototype javascript while perserving object reference and inheritance](http://stackoverflow.com/questions/15884096/organize-prototype-javascript-while-perserving-object-reference-and-inheritance) – Bergi May 12 '13 at 16:37

5 Answers5

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"
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • `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 May 11 '13 at 23:31
  • @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. – bfavaretto May 11 '13 at 23:50
  • @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. – Guffa May 12 '13 at 07:59
  • 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 – vsync May 12 '13 at 09:19
  • 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. – vsync May 12 '13 at 09:23
  • @vsync: No, that's not what you are looking for. That is not a good solution, because it only works if you only ever have one single instance of the `Game` object. If you create another instance of the `Game` object, it will steal the board from the first instance. – Guffa May 12 '13 at 12:42
  • yep duhh "WHAT WAS I THINKING?!" lol, obviously the prototype will change for every instance... – vsync May 12 '13 at 13:04
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)

Stephen
  • 5,362
  • 1
  • 22
  • 33
  • 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... – vsync May 11 '13 at 23:10
  • 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 – vsync May 11 '13 at 23:23
  • 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);` – Bergi May 12 '13 at 16:43
  • 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. – Stephen May 12 '13 at 18:37
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();
Steven Wexler
  • 16,589
  • 8
  • 53
  • 80
  • You are a good developer my friend, I love this second part. I've re-written it to better match – vsync May 12 '13 at 09:24
  • That only works for one single instance of the `Game` object. – Guffa May 12 '13 at 12:43
  • @Guffa you are right. Developers should not use this answer as is. – Steven Wexler May 12 '13 at 14:09
  • @Guffa I think my revised solution should work for multiple instances of Game. – Steven Wexler May 12 '13 at 14:48
  • 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 – Bergi May 12 '13 at 16:41
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

gurvinder372
  • 66,980
  • 10
  • 72
  • 94
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!
Qwertiy
  • 19,681
  • 15
  • 61
  • 128