0

I come mainly from a python background so I would expect for...in looping over an empty object {} to skip to loop all together. However this is not the case.

I have a loop that simplifies down to this:

for (let y in x) {
    console.log(x[y]);
    if (x[y]['amount'] >= 0){
       x[y]['amount'] -= 1;
    }
}

when the class method is run with x = {} it logs undefined indicating the loop has run.

Coming from python I would expect it to skip over the loop like this similar piece of code:

for y in x:
    print(y)
    if x[y]['amount'] >= 0:
        x[y]['amount'] -= 1

Where it does not print anything because the loop does not run.

Is there anyway to achieve a similar result in JavaScript where the loop would not run if the object is empty?

Thank you.

Ryan Schaefer
  • 3,047
  • 1
  • 26
  • 46
  • 2
    This should work as expected; are you running this by pasting code into the developer console? If so, what you see is simply the return value of your code. See [this SO question](https://stackoverflow.com/questions/14633968/chrome-firefox-console-log-always-appends-a-line-saying-undefined) for more info – Hamms Mar 12 '18 at 19:01
  • Well, I don't think in javascript, its any different. – binariedMe Mar 12 '18 at 19:01
  • You might be running into the issue that a `for...in` loop on an object will iterate over it's own keys as well as keys up the prototype chain. You can check [`hasOwnProperty`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty) or you could do a [`for...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) loop instead using [`Object.keys(x)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys): `for (let key in Object.keys(x))` – Alexander Nied Mar 12 '18 at 19:01
  • @phiter I tried to keep this as concise as possible but I think I'll edit it to include the full loop. – Ryan Schaefer Mar 12 '18 at 19:04
  • how about `console.log(y, x[y]);` so you see what keys it's iterating over – Thomas Mar 12 '18 at 19:07
  • it ended up being me missing a `eval()` because I was getting the data from `data-substrates` that I had assigned to html tags. So really an unrelated problem. Thanks everyone. – Ryan Schaefer Mar 12 '18 at 19:08

1 Answers1

4

For an empty object, that loop shouldn't run: https://repl.it/repls/SharpEducatedColdfusion. You may be just seeing the output undefined from the console as Hamms mentioned in the comments. Or there is a separate, unrelated issue / console.log giving you that output.


Update: I was curious, so I did some more digging. It turns out tslint (a linting tool for Typescript) actually requires a check in order to use the for...in loop for this exact reason. This answer does a great job of explaining the problem (which is the same problem had in this question) and providing strong solutions for it.

I'll summarize here. As I mentioned before, the essence of the problem is based on how the for...in loop looks for any properties on the prototype chain:

The loop will iterate over all enumerable properties of the object itself and those the object inherits from its constructor's prototype (properties closer to the object in the prototype chain override prototypes' properties). (MDN).

The solution that best fits your use case is:

for (let y in x) {
  if (x.hasOwnProperty(y)) {
    ...
  }
}

This ensures that y is actually defined on the object x, not just on it's prototype chain. For anyone who landed on this question that wants to loop over all the fields defined on the object, this solution is for you:

for (const field of Object.keys(x)) {
  ...
}

Something related which could cause confusion like this is that your x object or something it inherits from has a y property on it's prototype but that is not defined or has a falsy value on x. You can prevent that with:

if (x.y) {
 ... your logic / loop here ...
}

Note that the above check will match any falsy value.

If x.y is false or null or something else falsy, you can be more explicit with your check.

if (x.y !== undefined) {
 ... your logic / loop here ...
}

To learn more about how the compiler and let declaration keyword operate, I really like Kyle Simpson's book "You Don't Know JS - Scope and Closures".

vince
  • 7,808
  • 3
  • 34
  • 41
  • And what happens on `x = {y: false}`? What if you want to *use* that `false`? – Scott Sauyet Mar 12 '18 at 19:04
  • @binariedMe: The **answer** is about the console. That doesn't mean the question is, or that its code is.. – Scott Sauyet Mar 12 '18 at 19:06
  • @binariedMe, I explained why he is seeing what he's seeing in the console and gave him a way to get the functionality he's looking for. I'm open to constructive feedback on how to improve my answer. To Scott's question, I have updated my answer to account for the case you mentioned. – vince Mar 12 '18 at 19:07
  • @vincecampanale Actually the question is not about having undefined or null or false value as one of the property of object. If the object is empty itself, he's getting wrong console. As other comments suggests, it may be because he might be running it in browser's console. However in any case we don't have to go through the solution you gave for empty object. – binariedMe Mar 12 '18 at 19:09
  • @vincecampanale makes sense? – binariedMe Mar 12 '18 at 19:13
  • I don't really know what you mean by "wrong console". The question, as I understood it, is asking "Why is Javascript looping over nonexistent properties in my object?", which is the question I attempted to answer. I guess I'd need to look to OP for further clarification. Although it looks like he just accepted my answer so I guess that means this is the information he was looking for ¯\_( ' ' )_/¯ – vince Mar 12 '18 at 19:15
  • Well its surprising to see that the answer is accepted So cheers :) . But going by question, it clearly asks that why loop is running for empty object. Obviously JS won't run the loop and we don't need to put any check. But of course var x = { y: undefined, z: null } is not an empty object. While you'd agree that the loop won't run for var x = {} for sure and we don't need to check for undefined or null here, right? – binariedMe Mar 12 '18 at 19:22
  • 1
    Yep you're right...https://repl.it/repls/SharpEducatedColdfusion -- I'll fix up my answer, but leave the content in there because it's *somewhat* relevant. But I agree now that my answer is wrong as it is. That was a good catch, sorry for being slow on the uptake. – vince Mar 12 '18 at 19:33
  • @vincecampanale finally glad to upvote the answer. :) – binariedMe Mar 12 '18 at 19:47