To best understand the for..in and the for...of statements, we need to talk about enumerable properties and iterable objects from a high level.
Enumerable Properties and for...in Statements
Object properties are almost always seen as key/value pairs, but these properties also have attributes associated with them.
const house = {
cost: 300000,
rooms: 2,
baths: 2,
};
console.log(Object.getOwnPropertyDescriptor(house, 'cost'));
// {
// configurable: true
// enumerable: true
// value: 300000
// writable: true
// }
In the above example, we are using the utility Object.getOwnPropertyDescriptor()
to see the house
object's cost
property attributes.
We see that the cost
property has an attribute called enumerable
that is set to true
. Is that what makes a property be enumerable? For the sake of JavaScript, yes, this is exactly what makes a property enumerable.
What's so special about properties that are enumerable? Enumerable properties show up in for...in
loops (unless that property happens to be a Symbol). By default, all properties added to objects through normal assignment have their enumerable
property set to true
.
const apartment = {
rent: 1000,
rooms: 2,
baths: 1,
};
apartment.sqft = 950;
Object.defineProperty(apartment, 'floorPlan', {
value: 'A',
enumerable: false,
});
for (let prop in apartment) {
console.log(prop);
}
// rent
// rooms
// baths
// sqft
In this example, we have an object called apartment
that has some properties assigned a couple of different ways. We added properties directly within the object, and also via assignment.
We then went ahead and added a property called floorPlan
using the Object.defineProperty()
method. Adding a property this way allows us to change the attributes associated with that property. In this case, we wanted to explicitly change enumerable
to be false
.
What happens to this property when we run our for...in
loop? It doesn't show up! As I mentioned before, in JavaScript, only if the enumerable
property discriptor is set to true
, will that property be enumerable.
The
for...in
statement only loops over properties that are enumerable.
In this case, floorPlan
is not enumerable, so the for...in
loop ignores it.
One more thing to note about the for...in
statement is that it loops over properties, not its values.
Iterable Objects and for...of Statements
Iterable objects are objects that provide an interface that defines its iteration behavior over that object's values. A technical definition for this is that an object becomes iterable (“implements” the interface Iterable
) if it has a method (own or inherited) whose key is Symbol.iterator
. I'll go into a deeper explanation of this in another blog post.
For the most part, most built-in objects in JavaScript are iterable objects. Plain objects (as created by object literals), however, are not iterable.
How does for...of
fit in with all of this? for...of
is a loop that only works with iterable objects.
The
for...of
statement loops over iterable values that are defined to be returned by its iteration behavior.
Arrays
The most common iterable in JavaScript is the array. Its iteration behavior is pretty simple to define; it sequentially iterates over its elements. We can use a for...of
loop to test this out.
const belongings = [
'computer',
'home',
'car',
]
for (let item of belongings) {
console.log(item);
}
// computer
// home
// car
The Array object's iterator is responsible for giving us back each value defined in this array.
What would happen if we loop over belongings
with a for...in
loop instead, and how does this differ from a for...of
loop?
for (let prop in belongings) {
console.log(prop);
}
// 0
// 1
// 2
If you recall from above, by default, all elements defined in this array have their properties, 0
, 1
, and 2
, set with their attribute enumerable
equal to true
. This means we get back all its enumerable properties.
The for...of
loop, however, only returns back the values which are iterable.
An easy way to visualize this is by adding another property to this Array object using an assignment, and the Object.defineProperty()
method.
belongings.his = 'guitar';
Object.defineProperty(belongings, 'hers', {
value: 'phone',
enumerable: false,
});
for (let item of belongings) {
console.log(item);
}
// computer
// home
// car
for (let prop in belongings) {
console.log(prop);
}
// 0
// 1
// 2
// his
for...of
again only returned the direct values in the array (as defined in its iteration behavior). for...in
returned only those properties that have their enumerable
attribute set to true
. This is why we don't see the property hers
outputed.
Recap
Enumerable in JavaScript means properties whose enumerable
property descriptor is set to true
; These properties are what get returned to us in a for...in
loop.
Iterable objects are objects that define their own iteration behavior over that object's values; These are the values that get returned to us in a for...of
loop.