0

Run into an odd issue, perhaps best explained by code:

var fruits = ["apples", "oranges", "pears"];

var Breakfast = {
  _consume : function (fruit) {
    Breakfast[fruit]._numConsumed++;
  }
};

for (var f in fruits) {
  var fruit = fruits[f];
  Breakfast[fruit] = {
    consume : function () {
      Breakfast._consume(fruit);
    },
    _numConsumed: 0
  }
}

Breakfast.pears.consume();
Breakfast.pears.consume();

Breakfast.apples.consume();
Breakfast.apples.consume();
Breakfast.apples.consume();


console.log("Pears eaten: " + Breakfast.pears._numConsumed);
console.log("Apples eaten: " + Breakfast.apples._numConsumed);

The result of this is:

$ node example.js
Pears eaten: 5
Apples eaten: 0

Not quite sure how to overcome this behaviour?

Have I coded something wrong? Or is there a different pattern I should be using? (given that I want the "consume" function to be available to all my fruits etc)

Many thanks!

mark
  • 5
  • 1
  • Your `fruit` not locked, by the time you come to call `Breakfast.apples.consume()` the variable fruit is set to `pear`, so it calls your generic breakfast method which uses pear as the fruit it updates. – scragar Jul 08 '14 at 15:45

3 Answers3

0

You've run into some trouble with function closures. There's an excellent explanation in this question: How do JavaScript closures work?

The problem in your case is that this line:

  Breakfast._consume(fruit);

will always be equivalent to

  Breakfast._consume(fruits[2]);

once your for loop finishes running.

You could solve it by giving the whole loop its own context for each f:

for (var f in fruits) {
  (function(f) {
  var fruit = fruits[f];
  Breakfast[fruit] = {
    consume : function () {
      Breakfast._consume(fruit);
    },
    _numConsumed: 0
  }
  })(f);
}

... will result in...

"Pears eaten: 2"
"Apples eaten: 3"

But this is a rather crude solution. I'm sure you'll come up with a better way once you read through the explanations for how function closures work.

Community
  • 1
  • 1
blgt
  • 8,135
  • 1
  • 25
  • 28
0

The problem is that your variable will always be the last of the for loop. An easier way would to just define the property in the object you are creating.

i.e.

var fruits = ["apples", "oranges", "pears"];

var Breakfast = {
  _consume : function (fruit) {
    Breakfast[fruit]._numConsumed++;
  }
};

for (var f in fruits) {
  var fruit = fruits[f];
  Breakfast[fruit] = {
    id:fruit,
    consume : function () {
      Breakfast._consume(this.id);
    },
    _numConsumed: 0
  }
}
Michael Tempest
  • 814
  • 7
  • 12
-1

Edited answer:

var fruits = ["apples", "oranges", "pears"];

var Breakfast = {
  _consume : function (fruit) {
    console.log('other'+JSON.stringify(fruit));
    Breakfast[fruit]._numConsumed++;
  }
};

for (var f in fruits) {
  //var fruit = fruits[f];
  Breakfast[fruits[f]] = {
    fruit: fruits[f],
    consume : function () {
      Breakfast._consume(this.fruit);
    },
    _numConsumed: 0
  }
}

Breakfast.pears.consume();
Breakfast.pears.consume();

Breakfast.apples.consume();
Breakfast.apples.consume();
Breakfast.apples.consume();


console.log("Pears eaten: " + Breakfast.pears._numConsumed);
console.log("Apples eaten: " + Breakfast.apples._numConsumed);
Stevo Perisic
  • 353
  • 2
  • 12