Deep Understanding of JS Iteration Protocol-Handwritten Iterator

Posted by sandrol76 on Sat, 09 Nov 2019 08:04:15 +0100

Iterable and Iterator Protocols

Reference MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols

Iterable Protocol

Iterable protocols allow JavaScript objects to define or customize their iteration behavior, such as (define) what values can be looped in a for..of structure.Some built-in types are built-in iterative types and have default iteration behavior, such as Array or Map, while others are not (such as Object).
In order to become an iterative object, an object must implement the @@iterator method, meaning that the object (or an object on its prototype chain) must have a property named Symbol.iterator.
When an object needs to be iterated (for example, starting in a for..of loop), its @@iterator method is called with no parameters, and then returns an iterator that gets values in the iteration

iterator protocol

The iterator protocol defines a standard way to produce a finite or infinite sequence of values, and when all values have been iterated, there will be a default return value.
An object is considered an iterator only if it meets the following conditions:
It implements a next() method and has the following meanings
Returns a parameterless function of an object that has two properties:

  • done(boolean)

    • true: The iterator has exceeded the number of iterations that can be iterated.In this case, the value of value can be omitted
    • It is false if the iterator can produce the next value in the sequence.This is equivalent to not specifying the done attribute.
  • Value-Any JavaScript value returned by the iterator.Omit when done is true.

The next method must return an object with two necessary attributes: done and value. If a non-object value is returned (such as false and undefined), one will be displayed. TypeError Error in ("iterator.next() returned a non-object value")

var myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this }
}

Iterable Objects

  • Objects satisfying an iterative protocol are iteratable objects.
  • Iterable protocol: The [Symbol.iterator] value of an object is a parameterless function that returns an iterator (Iterator).
  • The data structure native to the Iterator interface is as follows.

    • Array
    • Map
    • Set
    • String
    • TypedArray
    • arguments object of function
    • NodeList object
  
   // for...of takes [Symbol.iterator]() of an iterator object and calls next() one by one on that iterator
   // The traversal ends until the iterator returns that the object's don property is true
    for (let value of ["a", "b", "c"]) {
      console.log(value);
    }

Handwriting an Iterator

    /* 
        This is a handwritten iterator (Iterator)
        Object that satisfies the iterator protocol.
        Iterator Protocol: The next method of an object is a parameterless function that returns an object with two properties, Don and value: 
    */
    var it = makeIterator(["a", "b"]);

    it.next(); // { value: "a", done: false }
    it.next(); // { value: "b", done: false }
    it.next(); // { value: undefined, done: true }

    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length
            ? { value: array[nextIndex++], done: false }
            : { value: undefined, done: true };
        },
      };
    }

Make your iterator iterative (iterators return iteratable objects)

A good iteration implements both the iterator protocol and the iterator protocol by returning the iterator protocol to itself

    /* 
        Making iterators iterative
        makeIterator Iterators generated by functions do not implement Iterable protocols
        Therefore, it cannot be used in grammars such as for...of.
        Iterable protocols can be implemented for this object, returning the iterator itself in the [Symbol.iterator] function
        createIterator from the new name of the following function
    */
    function createIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length
            ? { value: array[nextIndex++], done: false }
            : { value: undefined, done: true };
        },
        [Symbol.iterator]: function () { 
            console.log("Iterator returned:",this)
            return this // Notice that this is the object invocation mode, this is pointing to the upper object, the iterator
        }
      };
    }
    
    var iterator = createIterator([1, 2, 3]);
    console.log(...iterator)

What is a Generator

As you can see from the above functions, it's too cumbersome to write an iterator manually, so ES6 introduces a generator to make it easier to create an iterator.That is, a generator is a function that returns the iterator value.(Feels like the grammatical sugar of the function above)
Generator object is both an iterator and an iterator

function *aGeneratorfunction(){
  yield 1
  yield 2
  yield 3
};

var aGeneratorObject = aGeneratorfunction()

// Satisfies the iterator protocol and is an iterator
aGeneratorObject.next()   // {value: 1, done: false}
aGeneratorObject.next()   // {value: 2, done: false}
aGeneratorObject.next()   // {value: 3, done: false}
aGeneratorObject.next()   // {value: undefined, done: true}

// [Symbol.iterator] is a parameterless function that returns the generator object itself (an iterator) after execution and is therefore an iterator
aGeneratorObject[Symbol.iterator]() === aGeneratorObject   // true

// Can be iterated
var aGeneratorObject1 = aGeneratorfunction()
[...aGeneratorObject1]   // [1, 2, 3]

return in generator

Iteration ends when the done value of the traversed return object is true, and the value is not handled (return sets done to done)

function *createIterator() {
  yield 1;
  return 42;
  yield 2;
}

let iterator = createIterator();
iterator.next();   // {value: 1, done: false}
iterator.next();   // {value: 42, done: true}
iterator.next();   // {value: undefined, done: true}
let iterator1 = createIterator();
console.log(...iterator);   // 1

Generator Delegate yield*

function* g1() {
  yield 1;
  yield 2;
}

function* g2() {
  yield* g1();
  yield* [3, 4];
  yield* "56";
  yield* arguments;
}

var generator = g2(7, 8);
console.log(...generator);   // 1 2 3 4 "5" "6" 7 8

Topics: Javascript Attribute