DJG
DJG

Reputation: 495

Scope of "this" within object

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
}

Upvotes: 5

Views: 503

Answers (4)

kingd9
kingd9

Reputation: 89

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.

Upvotes: 0

GingerPlusPlus
GingerPlusPlus

Reputation: 5606

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
}

Upvotes: 1

The Dembinski
The Dembinski

Reputation: 1519

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.

Upvotes: 2

Piyush.kapoor
Piyush.kapoor

Reputation: 6803

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
}

Upvotes: 2

Related Questions