5

This (below) ended up giving me a "maximum call stack size exceeded" error. It seems like it's due to the way "this" is being interpreted within the "this.actions" object. Within that object, does "this" refer to that object, or the instance of the Unit class? If the former, would putting a .bind(this) on the end of the "this.actions" object make "this" refer to the class instance instead? If so, why? If not, why not?

function Unit(){
  this.move = function(direction){
    switch(direction){
      case 'up': { console.log('foo'); break; }
      case 'down': { console.log('foooo'); break; }
    }
    console.log('bar');
  }
  this.shoot = function(){console.log('zap')}

  this.actions = {
    'moveUp' : function(){ this.move('up') },
    'moveDown' : function(){ this.move('down') },
    'shoot' : function(){ this.shoot() }
  }

  return this
}
DJG
  • 435
  • 2
  • 14
  • you should not make `move` to be method of object. better make it simple function and call it like `'moveUp' : move.call(this, 'up') },` same with `shoot` – zb' Jun 30 '16 at 16:25
  • Sounds promising. Expand into an answer? – DJG Jun 30 '16 at 16:27

4 Answers4

2

Use bind

Reason:

lets say:

var r = new Unit();

When u call r.actions.moveup() , 'this' passed in the moveup function is actions.

function Unit(){
  this.move = function(direction){
    switch(direction){
      case 'up': { console.log('foo'); break; }
      case 'down': { console.log('foooo'); break; }
    }
    console.log('bar');
  }
  this.shoot = function(){console.log('zap')}

  this.actions = {
    'moveUp' : function(){ this.move('up') }.bind(this),
    'moveDown' : function(){ this.move('down') }.bind(this),
    'shoot' : function(){ this.shoot() }.bind(this)
  }

  return this
}
Piyush.kapoor
  • 6,041
  • 1
  • 17
  • 18
  • 1
    If stil use `this.move`, it is better to call it as `'moveUp' : this.move.bind(this, 'up');` – zb' Jun 30 '16 at 16:26
  • But doesn't the .bind here just bind actions to itself? – DJG Jun 30 '16 at 16:31
  • No - it doesn't. The `this` binding, when the constructor is invoked, is set to point to the new `Unit`. Therefor - the methods in the `actions` obj have their `this` binding set at conception to point to the new `Unit`. If you see my answer, I pass a `self` var to make it bit more explicit. – The Dembinski Jun 30 '16 at 16:36
2

The keyword this in the actions object will refer to the actions object.

Some possible fixes might look like:

function Unit(){
  var self = this;
  this.move = function(direction){
    switch(direction){
      case 'up': { console.log('foo'); break; }
      case 'down': { console.log('foooo'); break; }
    }
    console.log('bar');
  }
  this.shoot = function(){console.log('zap')}

  this.actions = {
    'moveUp' : function(){ this.move('up') }.bind(self),
    'moveDown' : function(){ this.move('down') }.bind(self),
    'shoot' : function(){ this.shoot() }.bind(self)
  }

  return this
}

Or, when you invoke those methods, you could use call or apply

eg:

var coolUnit = new Unit();
Unit.actions.moveUp.call(coolUnit);

Understanding this in the context of objects takes some work but here are some resources:

How does the "this" keyword work?

http://unschooled.org/2012/03/understanding-javascript-this/

http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

TL;DR - There are series of mental "rules" you can use to help keep track of what this is in a given context. Eg. The left-of-the-dot rule where the object to the left of the "dot" gets the this binding.

Object.foo() <- `this` in the method `foo` will point to `Object`

Using the "rule" mentioned above, you can rationalize that new Unit.actions.moveUp() would have the this binding set to point to the actions object because its left-of-the-dot.

Or you can use call/bind/apply to bind the this to the context you wish as shown above.

Community
  • 1
  • 1
The Dembinski
  • 1,294
  • 13
  • 22
1

Another solution: this is not a variable. Nested function have access to all variables defined in "parent" function (clousure) -- you can create variable, assign this to it, and use it instead of this, and it'll do what you want and expect:

function Unit(){
  var self = this;
  self.move = function(direction){
    switch(direction){
      case 'up': { console.log('foo'); break; }
      case 'down': { console.log('foooo'); break; }
    }
    console.log('bar');
  }
  self.shoot = function(){console.log('zap')}

  self.actions = {
    'moveUp' : function(){ self.move('up') },
    'moveDown' : function(){ self.move('down') },
    'shoot' : function(){ self.shoot() }
  }

  return self
}
GingerPlusPlus
  • 4,634
  • 1
  • 24
  • 47
0

Recently I ran across this article about this.

In section 4.2, he uses an example similar to your code and highlights the pitfall of forgetting 'new'. 'this' in a function invocation points to the global object (window or browser) so when you return it, you put your functions onto the global object and then return a reference to it. If you would have used new Unit() and don't return this, you would get an object with your functions on it.

You could use bind, but I think it would be something like Unit.bind(Unit) which would be look weird.

You could also use a factory function to return the object and you won't have to worry about forgetting new

function Unit(){

  var move = function(direction){
    switch(direction){
       case 'up': { console.log('foo'); break; }
       case 'down': { console.log('foooo'); break; }
    }
    console.log('bar');
  };
  var shoot = function(){console.log('zap')};

  var actions = {
    moveUp : function(){ move('up') },
    moveDown : function(){ move('down') },
    shoot : shoot
  };

 return {
    actions:actions
 };
}

var player1=Unit();

player1.actions.shoot();
player1.actions.moveDown();
player1.actions.moveUp();

I removed the this from the call to move, part of the problem is actions is an object not a function, so while you could use bind on the functions inside the object, you can as I have in the code, just close over the functions and then expose it.

Also if you use the self=this and don't use the new keyword, you still have reference to the window/browser object and are creating global scope.

kingd9
  • 79
  • 5
  • Edited and it works now. Not sure am I being clear on why this works. I can add more if you like – kingd9 Jun 30 '16 at 17:55