Captainlonate
Captainlonate

Reputation: 5008

What is the meaning of *[Symbol.iterator] in this context

I found some code online. I've squashed the original code down into this little excerpt, that when ran, will print 1-20 to the console.

var NumbersFromOne = {
  *[Symbol.iterator] () {
    for (let i = 1;; ++i) yield i;
  }
};

var take = function* (numberToTake, iterable) {
  let remaining = numberToTake;

  for (let value of NumbersFromOne) {
    if (remaining-- <= 0) break;
    yield value;
  }
}


var printToTwenty = take(20, NumbersFromOne)

console.log(...printToTwenty);

Now, I understand that take() is a GeneratorFunction.
When take() is called, it is given an iterator.
The code "...printToTwenty" uses the spread operator to iterate through that function.

I understand that NumbersFromOne is an object. I've come here looking for an explanation of what this part means:

*[Symbol.iterator] () {}

Declaring generator functions is done like this: function* () {}
So I'm assuming this isn't declaring a generator function.

* also doesn't represent the function name
* also can't be replaced with another operator (/, -, +)

What is the deal with that syntax, and why is the * before [Symbol.iterator]
If placed after, it will not run.
I had considered that *[Symbol.iterator] () is a way to overwrite the existing iterator property, but then wouldn't it say this[Symbol.iterator].

Thanks!

Upvotes: 27

Views: 13671

Answers (1)

nils
nils

Reputation: 27174

There are a few things that might make this code look complicated:

It uses the object property shorthand notation. What you're seeing here is actually the following:

var NumbersFromOne = {
  [Symbol.iterator]: function* () {
    for (let i = 1;; ++i) yield i;
  }
};

Symbol.iterator creates a custom iterator for your NumbersFromOne object.

So your code basically means that the iterator of NumbersFromOne is defined as a generator. Instead of manually having to define a function which returns a next and other properties:

var NumbersFromOne = {
  [Symbol.iterator]: function () {
    var i = 1;
    return {
        next: function() {
            return { value: i++, done: false };
        }
    };
  }
};

Returning the generator creates the next function automatically for. This allows you to yield when you need to.

It can then be called as:

const it = NumbersFromOne[Symbol.iterator]();
it.next(); // 1
it.next(); // 2
it.next(); // 3
// ...

Note: Written this way, this iterator never ends! So if you were to call it in a for ... of loop without an end-condition, it would freeze your program.

Upvotes: 36

Related Questions