Antonio Pavicevac-Ortiz
Antonio Pavicevac-Ortiz

Reputation: 7759

Why doesn't my object's property update in JavaScript?

I have a function, which I have prepared for a constructor call...

function Queue() {
  if (!(this instanceof Queue)) return new Queue();
    this.capacity = {};
    this._count = 0;
}

And these method are being set on the the prototype property of Queue...Everything kosher right?

  Queue.prototype.enqueue = function(name, options) {
    this.capacity[name] = options || {};
    this.count();
    if (this._count > 5) {
      return 'Max capacity has been reached—which is five, please dequeue....'
    }
  };

  Queue.prototype.count = function() {
    var total = Object.keys(this.capacity);
      total.forEach(function(elem) {
        this._count++
      });
      if (this._count == 1) {
        console.log(this.capacity[Object.keys(this.capacity)])
      console.log( 'There is one item in the queue');
      } else {
        console.log(this.capacity[Object.keys(this.capacity)])
        console.log( 'There are ' + this._count + ' items in the queue');
      }
   };

My question how do i get this._count to increment when the enqueue/count method fires? I keep getting:

There are 0 items in the queue

I know I could add it on the .prototype property and place that in the count function and have it reference a local var...

Queue.prototype.count = function() {
    var total = Object.keys(this.capacity), count = 0;
    total.forEach(function(elem) {
      this.count++
    });

Queue.prototype.call = call // <-- weird no?

if (this.count == 1) {
    console.log(this.capacity[Object.keys(this.capacity)])
  console.log( 'There is one item in the queue');
} else {
     console.log(this.capacity[Object.keys(this.capacity)])
  console.log( 'There are ' + this.count + ' items in the queue');
}

};

But that seems not to be elegant...

Thanks in advance!

Upvotes: 0

Views: 50

Answers (3)

aaronjkrause
aaronjkrause

Reputation: 849

The existing answers provide good solutions to the problem itself, I just thought I'd elaborate a bit more on the why.

this is a reference assigned by the execution context. More plainly it's a reference that's determined by the call site of the function. Since you can pass functions around in JavaScript like any other value this can lead to problems being caused by that reference being a moving target.

The issue with your code is that you're referring to this inside of a forEach. forEach takes a function as an argument and calls it, since what this is pointing to is determined by where the function is called and not where it's defined the value is something different when it gets called. It ends up falling back to whatever global context or undefined if you're in strict mode.

There are a number of different ways to handle the problem.

You could store a reference to the outer this on a variable and use it inside the other function.

var self = this;
total.forEach(function(elem) {
  self._count++;
});

You could use .bind. It's a function method which returns a function that uses the passed in object as the reference for this no matter where you call it.

total.forEach(function(elem) {
  this._count++;
}.bind(this));

Or you could use an arrow function. Arrow functions don't create their own context so they'll just maintain the value of this from the surrounding one.

total.forEach(() => {
  this._count++;
});

This is a common problem and these are all valid solutions. They go from least to most elegant in my opinion.

Upvotes: 1

rkapl
rkapl

Reputation: 990

Try following modification (bind the function):

total.forEach(function(elem) {
   this._count++
}.bind(this));

The problems is that this refers to a different object than in the parent function, because in JS, closures do not preserve this but instead the caller decides the this value. Alternatively, you can use the second thisArg argument of foreach.

Upvotes: 1

karthick
karthick

Reputation: 12176

You need to bind this within forEach

Queue.prototype.count = function() {
    var total = Object.keys(this.capacity);
      total.forEach(function(elem) {
        this._count++
      }.bind(this)); //bind the context
      if (this._count == 1) {
        console.log(this.capacity[Object.keys(this.capacity)])
      console.log( 'There is one item in the queue');
      } else {
        console.log(this.capacity[Object.keys(this.capacity)])
        console.log( 'There are ' + this._count + ' items in the queue');
      }
   };

Upvotes: 2

Related Questions