Ryan Schaefer
Ryan Schaefer

Reputation: 3120

Skip over a for loop if object is empty

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.

Upvotes: 0

Views: 3820

Answers (1)

vince
vince

Reputation: 8306

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".

Upvotes: 4

Related Questions