0, preface

ES6 introduced the concept of iterators and iterables and provided support for iterables, such as for… Of loops, Map(iterable) constructors, unwrapping syntax… And so on. Let us also greatly simplify the traversal of the data set outside the array.

iterators

An iterator is a concept borrowed from C++ and other languages. Like a pointer, an iterator points to an element in a data set. You can get what it points to, and you can move it to get other elements. Iterators are similar to the extension of subscripts in arrays. Various data structures, such as lists, sets and maps, all have corresponding iterators.

Iterators in JS are designed specifically to iterate over this operation. Each fetched iterator always initially refers to the first element, and the iterator has only one behavior, next(), until the last element of the data set is fetched. We cannot move iterators flexibly, so the iterator’s job is to traverse the elements of the dataset in some order.

JS states that the iterator must implement the next() interface, which should return the current element and point the iterator to the next element in the form of {value: the element value, done: whether the iterator is done}, where done is a Boolean value. If the done attribute is true, we will not read value by default, so {value: undipay, done: true} is often returned. Therefore, I pay something like {value: 2, done: True} does not cause an error, but because done is set to true, for… The value of an operation such as “of” is ignored. Therefore, done:false and value: undipay can be omitted. A simple JS iterator looks like this:

let iter = {
    i: 0.next() {
        if (this.i > 10) return { done: true };
        return { value: this.i++ }; }}// Use iterators manually
console.log(iter.next());  //{ value: 0 }
console.log(iter.next());   //{ value: 1 }
while (true) {     
    let item = iter.next();
    if(! item.done) {console.log(item.value);     // Prints from 2 to 10
    } else {
        break; }}Copy the code

As you can see, an iterator is no different from a normal JS object; it is just an object used to implement iteration. Iterators are not very practical to operate manually. The purpose of iterators is to attach to objects and make an object, or data structure, iterable.

Iterator interface with iterable

The iterator interface is the default interface that we call when we get an object iterator. An object that implements the iterator interface is an iterable. The default iterator interface for JS is symbol. iterator. An object that implements the [symbol. iterator] interface becomes an iterable.

Iterator [Symbol. Iterator] is a special Symbol property that is used internally by JS to check whether an object is iterable. The meaning of the word interface is that it is a function whose result should be put back into an iterator. The iterator above must have a next() operation, so it should be possible to call the chain iterableObj[symbol.iterator]().next() for iterables. Arrays are the most representative iterable. Let’s test them out:

arr = [1.'2', {a: 3}];
let arrIt = arr[Symbol.iterator]();    // Get an array iterator
console.log(arrIt.next());   //{ value: 1, done: false }
console.log(arrIt.next());  //{ value: '2', done: false }
console.log(arrIt.next());   //{ value: { a: 3 }, done: false }
console.log(arrIt.next());  //{ value: undefined, done: true }
Copy the code

As you can see, the next() interface to the iterator does exactly what it wants and returns the above structure.

3. Customize iterables

Now, let’s implement a few iterables. This is as simple as:

  1. Implements an iterator interface for an object[Symbol.iterator]()Notice it’s a method,
  2. Returns an iterator object in the iterator interface,
  3. Make sure the iterator object hasnext()Interface, and returns{value: v, done: bool}The structure of the.

3.1 Iterable Range objects

As the first iterable, we implement something similar to python’s range(from, to), except that we use the range object to encapsulate a closed and open range [from, to].

function Range(from, to) {
    this.from = from;
    this.to = to;
}
Range.prototype[Symbol.iterator] = function () {
    // Return an iterator
    return {
        cur: this.from,
        to: this.to, // Make sure it is available in next()
        next() {
            return (this.cur < this.to) ? {
                value: this.cur++,
                done: false}, {value: undefined.done: true}; }}}let range = new Range(5.11);  // Create a range object
/ / used for... Of circulation
for (const num of range) {
    console.log(num);    // print 5,6,7,8,9,10
}
/ / use
let arrFromRange = Array.from(range);
console.log(arrFromRange);   / /,6,7,8,9,10 [5]
Copy the code

3.2. Use Generator functions as iterator interfaces

Because Generator objects generated by Generator functions are a special kind of iterator, we can use Generator functions as iterator interfaces to objects in a very methodical way. Overwrite the iterator interface using Generator:

Range.prototype[Symbol.iterator] = function* () {
    for (let i = this.from; i < this.to; i++) {
        yieldi; }}Copy the code

This method is more concise and is the most recommended. The values generated in the Generator function are the values obtained during the traversal.

3.3. Iterable List

Next, we define a List node, List, where we leave out unnecessary interfaces.

function ListNode(value) {
    this.value = value;
    this.nextNode = null;
}

function List(root) {
    this.cur = this.root = root;
}
// Next interface to List
List.prototype.next = function () {
    if (this.cur) { // Non-tail sentry node
        let curNode = {
            value: this.cur.value
        };
        this.cur = this.cur.nextNode;
        return curNode;
    } else {
        return {
            done: true}; }}Copy the code

List.next() implements the manipulation of moving the List pointer back and returns the value of the node before it was moved. You may notice that I deliberately formatted the object to be the same as the iterator returns, and you’ll see why below. Now we make the List iterable, as we did before, so that List[symbol.iterator]().next() returns the correct {value: v, done: true} format. Yes, we have the dragon, just one finishing touch:

List.prototype[Symbol.iterator] = function () {
    return this;
}
Copy the code

Write a test on the fly:

let a = new ListNode('a'),
    b = new ListNode('b'),
    c = new ListNode('c');
a.nextNode = b, b.nextNode = c;
let list = new List(a);
for (let i of list) {
    console.log(i);  //a,b,c
}
Copy the code

Perfect! The iterator interface of the List returns itself, using its own next() interface to do the iteration, which means that the iterator of the List is the List itself, which I think is clever for my example.

3.3. Iterable iterators

The List example above is a bit of a stretch, as the return value of list.next() is required by the iterator to normally use let curValue = list.next().value to receive the returned node value correctly, indeed. There is, however, a time when it makes sense to call an iterator an iterable because it is an iterator, and to make itself an iterator is just as natural as 1=1.

Going back to the beginning, we just need to tweak it a little bit

let iter = {
    i: 0.next() {
        if (this.i > 10) return { done: true };
        return { value: this.i++ };
    },
    // Let the iterator interface return itself
     [Symbol.iterator]() {
        return this; }}// This way, you can use the iterator for any iterable
for (let i of iter) {
    console.log(i);  
}
Copy the code

In this way, the iterator itself is iterable. Iterators with built-in iterators are also iterable. Operations like for(let I of arr[symbol.iterator]()) are possible. The principle is that Array iterators inherit array.prototype. Other types have similar inheritance, such as Generator and Generator object.

4. The meaning of iterable

As an extension of arrays, iterable objects have special significance. Previously, arrays were the only structure supported by an interface that needed to manipulate a set of data. Non-array objects had to be converted into arrays somehow, and when they were done, they might need to be restored back to the original structure, a cumbersome back-and-forth that wasn’t ideal. Given the concept of an iterable, this kind of operation can take an iterable, and arrays are iterable, so the array arguments are still valid, and then anything that implements an iterable interface is also valid. Consider the following example:

function sumInArray(arr){
    let sum=0;
    for(let i = 0; i<arr.length; i++){ sum+=arr[i]; }return sum;
}
function sumInIterable(可迭代){
    let sum = 0;
    for(let num of iterable){
        sum+=num;
    }
    return sum;
}
Copy the code

SumInArray () is friendly only to arrays, whereas sumInIterable() is common to all iterable objects, such as Range objects and iter objects. Yes, the move from arrays to iterables represents an increase in the versatility of the interface. This is so obvious that you might think I’m talking nonsense. Do you consider using iterables instead of arrays when designing your interfaces? Personally, I find this improvement to be beneficial in many cases, especially in the case of interfaces with more application scenarios, and I find that many ES6 operations are also based on iterables. If you have any ideas, please feel free to discuss them in the comments section.

5. Use iterables

First, consider the iterable built into JS:

  1. Non-weak data structures, includingArray.Set.Map.
  2. The NodeList object in the DOM.
  3. String object.
  4. The arguments property of the function.

See also which operations are based on iterables:

  1. for... ofgrammar
  2. . 可迭代: Expand syntax and deconstruct assignment
  3. yield*grammar
  4. Map.Set.WeakMap.WeakSetConstructor of. Why is there no Array? Because Array treats its object as an element, but it doesArray.from(iterable).
  5. Object.fromEntries(iterable), the result of each iteration should be a binary array of corresponding key-value pairs, andMapThe results of iteration are consistent, oftenlet obj = Object.fromEntries(map)Implement the transformation from map to Object.
  6. promise.all(iterable)andpromist.race(iterable).

I don’t think specific uses of these methods should be included here, but if you’ve used them, you’ll understand, just remember that they are supported for any iterable. I can’t go through all of them without knowing them, so you should study them all.

6, afterword.

It took me a whole afternoon to write an article. It was so slow. The content is very basic, but it is basically tested by myself, sorted out, integrated a lot of my own thinking, also found a lot of details, harvest quite a lot. As expected on the paper come zhongjue shallow, predict this need to practice.

I am Czpcalm, hope to write more technical articles with independent ideas, welcome to like, attention, thank you!