4

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?
Andrew Li
  • 55,805
  • 14
  • 125
  • 143
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • 1
    Check the last step of the [Forin/Of**Head**Evaluation](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-runtime-semantics-forin-div-ofheadevaluation-tdznames-expr-iterationkind) – Bergi Oct 10 '17 at 11:48
  • 1
    "*if not specified explicitly the for..of loop uses the iterator returned by `map.entries()` call.*" - not exactly. It always uses `map[Symbol.iterator]()`, [which is the same method as `entries` though](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/@@iterator). If you pass an iterator object, it does call `…[Symbol.iterator]()` on those as well, it's just that the method returns the object itself (`return this;`) on iterator instances. – Bergi Oct 10 '17 at 11:51
  • @Bergi, thanks a lot for the confirmation, that's what I thought. – Max Koretskyi Oct 10 '17 at 12:03

2 Answers2

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.

Community
  • 1
  • 1
Andrew Li
  • 55,805
  • 14
  • 125
  • 143
  • thanks a lot, that's what I was looking for. `GetIterator ( obj [ , method ] )` can take optionally a method, and from `7b` I can see that the method is not specified hence `Set method to ? GetMethod(obj, @@iterator).`. Correct? Also I'm wondering what is this question mark before `GetMethod`. Any ideas? – Max Koretskyi Oct 10 '17 at 11:58
  • @AngularInDepth.com Yes. Only *exprValue* is passed. – Andrew Li Oct 10 '17 at 12:00
  • @AngularInDepth.com That’s described here: https://stackoverflow.com/questions/41733976/ecmascript-specification-meaning-of-question-mark-in-the-spec – Andrew Li Oct 10 '17 at 12:08
1

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; }
Community
  • 1
  • 1
trincot
  • 317,000
  • 35
  • 244
  • 286