How does `for..of` loop resolve the iterator from

2020-04-09 07:07发布

问题:

For an object to implement iterable interface it must implement [Symbol.iterator] key that points to a function that returns the iterator. I'm wondering if the for..of loop internally calls this method on an object to get that iterator?

The reason I'm curious is that, for example, Map defines an interface with several iterators (entries, values, keys) and it seems that if not specified explicitly the for..of loop uses the iterator returned by map.entries() call.

I've trying searching in the specification but it only specifies that iterator is passed as a parameter to the abstract operation ForOf:

The abstract operation ForIn/OfBodyEvaluation is called with arguments lhs, stmt, iterator, iterationKind, lhsKind, and labelSet.

So basically two questions:

  1. How is iterator obtained from an object?
  2. Where is it specified in the specification?

回答1:

The specific place where the operation is specified is in 7.4.1 GetIterator( obj [ , method ] ). This gets the @@iterator property of the passed object in step 1a. of the abstract operation:

a. Set method to GetMethod(obj, @@iterator).

@@iterator is a well-known symbol that is the Symbol.iterator property on objects.

This is used by for-in and for-of loops due to the productions in 13.7.5.11 Runtime Semantics:

IterationStatement : for(ForDeclaration of AssignmentExpression) Statement

  1. Let keyResult be the result of performing ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, AssignmentExpression, iterate).
  2. Return ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, iterate, lexicalBinding, labelSet).

Here, you can see the iterator argument passed to ForIn/OfBodyEvaluation is the return value keyResult of ForIn/OfHeadEvaluation. The return value is, in step 7b:

b. Return GetIterator(exprValue).

Thus, for-of loops get the iterator by accessing the @@iterator or Symbol.iterator well-known symbol by specification.



回答2:

An object can define only one symbol Symbol.iterator, which is the one that will be called on iteration on the object itself. The other properties of the object, such as the examples you have given (entries, keys, values) may also return an iterator, but those are in general not the same iterators. They could be the same, but that is just an implementation choice. There is no ambiguity as to which iterator is called when iterating the object with for..of. It is the one returned by [Symbol.iterator].

  1. How iterator is obtained from an object?

You can get it by calling the function keyed with Symbol.iterator, e.g.

const iterator = obj[Symbol.iterator]();

It is retrieved implicitly with for..of.

  1. Where is it specified in the spec?

This table in the specs explains:

@@iterator "Symbol.iterator"

A method that returns the default Iterator for an object. Called by the semantics of the for-of statement.

Here is how you can make a custom function for returning the default iterator for an object (overwriting the default one), and see how it gets called:

const obj = {
    // Define a custom function for returning the default iterator for this object
    [Symbol.iterator]: function () {
        console.log('The iterator-returning function got invoked');
        // Return an iterator from some other object
        //  (but we could have created one from scratch as well):
        return 'abc'[Symbol.iterator]();
    },
    myMethod: function () {
        // This method happens to return the same iterator
        return this[Symbol.iterator]();
    },
    myOtherMethod: function () {
        // This method happens to return another iterator
        return 'def'[Symbol.iterator]();
    }
}

for (const a of obj) {
    console.log(a); // a b c
}
for (const a of obj.myMethod()) {
    console.log(a); // a b c
}
for (const a of obj.myOtherMethod()) {
    console.log(a); // d e f
}
.as-console-wrapper { max-height: 100% !important; top: 0; }