You-Dont-Know-JS icon indicating copy to clipboard operation
You-Dont-Know-JS copied to clipboard

this & Object Prototypes - Chapter 3: Symbol.iterator enumerablility

Open Beaglefoot opened this issue 5 years ago • 3 comments

In the end of the chapter there's an example and statement:

var myObject = {
	a: 2,
	b: 3
};

Object.defineProperty( myObject, Symbol.iterator, {
	enumerable: false,
	writable: false,
	configurable: true,
	value: function() {
		var o = this;
		var idx = 0;
		var ks = Object.keys( o );
		return {
			next: function() {
				return {
					value: o[ks[idx++]],
					done: (idx > ks.length)
				};
			}
		};
	}
} );

// iterate `myObject` manually
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefined, done:true }

// iterate `myObject` with `for..of`
for (var v of myObject) {
	console.log( v );
}
// 2
// 3

Note: We used Object.defineProperty(..) to define our custom @@iterator (mostly so we could make it non-enumerable), but using the Symbol as a computed property name (covered earlier in this chapter), we could have declared it directly, like var myObject = { a:2, b:3, [Symbol.iterator]: function(){ /* .. */ } }.

I'm somewhat confused by the part:

(mostly so we could make it non-enumerable)

MDN states that Symbol.iterator is non-enumerable, my own experimentation is a little bit confusing...

const iterable = {
  a: 1,
  [Symbol.iterator]: function() {}
};

for (x in iterable) console.log(x); // a

console.log(iterable.propertyIsEnumerable(Symbol.iterator)); // true ...WTF?!

Is there something else we should know?

Beaglefoot avatar Dec 11 '18 21:12 Beaglefoot

This detail in the spec changed after publication of the book, which predated the spec by more than a year.

getify avatar Dec 11 '18 23:12 getify

@Beaglefoot Did you get the answer?

According to MDN, Symbol.iterator is non-enumerable. It is correct absolutely. Note that Symbol.iterator is also property of Symbol:

Object.getOwnPropertyDescriptor(Symbol, 'iterator');
// {value: Symbol(Symbol.iterator), writable: false, enumerable: false, configurable: false}

You misunderstood its meaning. That doesn't mean that Symbol.iterator property of any object is also non-enumerable by default. Instead, its enumerable can be different by how to define it. Let's check:

var object = {
  [Symbol.iterator]: function() {}
};

Object.getOwnPropertyDescriptor(object, Symbol.iterator);
// {value: ƒ, writable: true, enumerable: true, configurable: true}
object.propertyIsEnumerable(Symbol.iterator); // true (of course).

// What about to change its `enumerable`?
Object.defineProperty(object, Symbol.iterator, {
  enumerable: false
});
object.propertyIsEnumerable(Symbol.iterator); // false

It is helpful to think that an object uses Symbol.iterator property which is property of Symbol as its own property of the your object.

But, why the Symbol.iterator doesn't appear in for...in loop? That's because a symbol doesn't appear in the for...in loop. You can check it from MDN here:

A for...in loop only iterates over enumerable, non-Symbol properties...

Object.defineProperty(object, Symbol.iterator, {
  enumerable: true
});
object.propertyIsEnumerable(Symbol.iterator); // true

for (let prop in object) {
  console.log(prop);
} // nothing printed.

ghost avatar Jul 13 '19 10:07 ghost

Upd: seems I am a little bit late with an answer... But here it is anyway.

@jinbeomhong Ok, I may misinterpret this, by here's my explanation.

First of all, by default enumerable property descriptor is set to true

({ a: 1 }).propertyIsEnumerable('a'); // true

The same is true for user defined symbol properties.

const mySym = Symbol('js is weird');
({ [mySym]: 1 }).propertyIsEnumerable(mySym); // true

Arrays, strings and other built-in iterables have predefined Symbol.iterator property, whose enumerable property descriptor is set to false.

([]).propertyIsEnumerable(Symbol.iterator); // false
('').propertyIsEnumerable(Symbol.iterator); // false

But if you try to redefine this property, then default property descriptor rules will take place.

var arr = [];
arr[Symbol.iterator] = () => {};
(arr).propertyIsEnumerable(Symbol.iterator); // true

The second part is actual traverse over properties. And spec says:

next method iterates over all the String-valued keys

So that's why we don't see these enumerable properties in for...in loop.

Beaglefoot avatar Jul 13 '19 11:07 Beaglefoot