Reputation: 2707
I was just wondering if there is a way to make an object property enumerable like in a for in
loop but not show up in a for of loop
sort of like
Object.defineProperty({},'prop',{
enumerable:true,
iterable:false
}
if not, are there any plans to implement a feature like this? Or does the for of
loop use the enumerable property
Upvotes: 6
Views: 3181
Reputation: 665276
The difference between for in
and for of
evaluations is that one uses obj.[[enumerate]]
and the other uses GetIterator(obj)
to determine the looped items. [[enumerate]]
does (unless obj
is a Proxy)
return an Iterator object whose
next
method iterates over all the String-valued keys of enumerable properties ofobj
.
GetIterator
will (try to) call the @@iterate
method - however, normal objects don't implement this interface.
So no property will show up in for of
loops unless you explicitly state it:
function defineIterableProperty(obj, prop, desc) {
let old = obj[Symbol.iterate];
Object.defineProperty(obj, prop, desc);
obj[Symbol.iterate] = function* () {
if (old) yield* old.call(this);
yield prop;
};
return obj;
}
(Untested, but I hope you get the idea)
Upvotes: 2
Reputation: 95
I did some digging around at the Mozilla Development Network (MDN).
Turns out that objects have a obj.propertyIsEnumerable(prop)
method to check if the property is enumerable. From the examples given in the MDN, properties inherited through the prototype chain are not enumerable, thus the method returns false for those properties.
An example of non enumerable properties are constructors and the property length
of arrays.
For the iterable part of the question, I will quote MDN: "Iterable in ECMAScript 6 is an interface (or in other words, a protocol), not a type of object like Array or Map".
That is to say, the object needs to implement this interface to make its properties iterable. This is the case for built in iterables such as String, Array, Map, Set, WeakSet and Generator objects.
The following code illustrated this:
var aString = "hello"
typeof aString[Symbol.iterator] // "function"
aString[Symbol.iterator]() + "" // "[object String Iterator]"
[...aString] // ["h", "e", "l", "l", "o"]
Of course, you can define your own iterator implementation.
Coming back to the question, properties attached to the object or its prototype (directly, meaning not via inheritance) will be displayed as enumerable in the for...in
loop. For the iterables you need an implementation, either the ones mentioned before, or your own. Here is a great example from MDN.
let arr = [ 3, 5, 7 ];
arr.foo = "hello";
for (let i in arr) {
console.log(i); // logs "0", "1", "2", "foo" these are property names or indexes
}
for (let i of arr) {
console.log(i); // logs "3", "5", "7" these are actual values of an
// iterable implementation provided by the array prototype
}
The let
keyword is equivalent in this context to a var
definition (though it has more implications but they are outside the scope of this post).
As you can see the array has an implementation already of the iterable
interface (from the array prototype), thus it yields its values when using the for...of
loop, whereas the foo
property is not displayed (neither value nor property name).
So if your object does not implement an iterable
interface, it should not be iterable (in principle) and thus it will display its properties' in a for...of
loop.
Upvotes: 5