探索ES6 Iterator(遍历器)

前言:

半年前快速过了一遍ES6语法,掌握并熟练了一些常用的ES6语法,比如:class、继承、模板字符串、解构赋值、导入导出、Promise等等,然而对于一些ES6的其他新特性,并没有认真研究,最近学习ES8时,学习了Object.entries方法,然后遇到了一个问题,这个问题是:为什么对象不可以用for of进行遍历?这才来研读了一下Iterator,很庆幸我在这里找到了答案,本文参考了阮一峰老师的ES6标准入门,为此买了一本纸质版的ES6标准入门以示感谢!

1、Iterator是什么?

我的理解就是为不同的数据结构统一遍历接口,以便按顺序进行遍历,通俗一点讲就是可以共用ES6的for of遍历机制

2、一起来看看ES6标准入门中关于Iterator的简单模拟实现
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};
    }
  };
}

首先调用makeIterator方法得到一个遍历器对象,该对象里面有一个next属性对应一个方法,该方法用于进行遍历,该方法共享闭包作用域里面的nextIndex指针来进行遍历,每次执行next方法都会将nextIndex加1,指向数组的下一个成员,当指针nextIndex大于数组长度时,将返回{value: undefined, done: true},即完成状态。此处抛一个问题:for of遍历数据结构时是依据什么跳出遍历过程的?

3、Iterator接口部署在哪里

并不是所有的数据结构都部署了Iterator接口,只有部署了Iterator的数据结构才可以使用for of进行遍历,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性

console.log(Object.prototype[Symbol.iterator])
//undefined
console.log(Array.prototype[Symbol.iterator])
//function values() { [native code] }
console.log(String.prototype[Symbol.iterator])
//function [Symbol.iterator]() { [native code] }

这说明对象是没有部署Iterator接口的,而数组和字符串是部署了遍历借口的,下面我们来验证一下:

const obj={
    "name":"luochao",
    "age" : "age"
}
for (const x of obj) { 
    console.log(x); //obj[Symbol.iterator] is not a function
}
for (const y of ["1","2","3"]) { 
    console.log(y);  //1,2,3
}
for (const z of "lc") { 
    console.log(z);  //l,c
}

当然你也可以这样遍历

let obj4= [6,7,8]
let xx=obj4[Symbol.iterator](); //拿到遍历器对象
for(let i=0;i<obj4.length;i++){
    console.log(xx.next())
}
//Object {value: 6, done: false}
//Object {value: 7, done: false}
//Object {value: 8, done: false}
console.log(xx.next())// {value: undefined,done: true}

此处抛出一个问题?你是否发现这两种遍历数组的方式差别在哪里了呢?你是否可以模拟的写出Array.prototype[Symbol.iterator]遍历接口的实现逻辑呢?聪明的你可能已经想出来了

The data structures with natively deployed traversal interfaces include: Array, Map, Set, String, TypedArray, and arguments objects of functions.

If you haven’t written the implementation of the Array.prototype[Symbol.iterator] traversal interface, don’t be discouraged. Follow me and read on.

4. If an object wants to have an Iterator interface that can be called by a for...of loop, it must deploy the traverser generation method on the property of Symbol.iterator (the object on the prototype chain can also have this method). It doesn’t matter if you don’t understand it. See Let’s take a look at the code in the ES6 standard gate
class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}

Here we look at the code compiled using typescript

var RangeIterator = (function () {
    
    
    function RangeIterator(start, stop) {
    
    
        this.value = start;
        this.stop = stop;
    }
    RangeIterator.prototype[Symbol.iterator] = function () {
    
     return this; };
    RangeIterator.prototype.next = function () {
    
    
        var value = this.value;
        if (value < this.stop) {
            this.value++;
            return { done: false, value: value };
        }
        return { done: true, value: undefined };
    };
    return RangeIterator;
}());

Through the compiled code, we can find that the traverser interface has been added to RangeIterator.prototype[Symbol.iterator]. What did we find? for of traverses the object generated by range(0, 3) and gets the value directly. Can it be regarded as the object generated by range(0,3) directly calling the [Symbol.iterator] method on the prototype RangeIterator.prototype to get the traversal device object, and then called the next method on the prototype to get the { done: false, value: value } object, and then took the value attribute of the object to get the actual value: 0, 1, 2, is for of true? Did you do these things?

5. Let’s rewrite the Array.prototype[Symbol.iterator] function (this is just my assumption, please correct me if I’m wrong)
Array.prototype[Symbol.iterator]=function(){
    
    
    this.nextIndex=0;
    this.next=function(){
    
    
        return this.nextIndex < this.length ?
        {value: this[this.nextIndex++], done: false} :
        {value: 1, done: true};
    }
    return this;
}
let arr3=[9,8,7];
for(let attr of arr3){
  console.log(attr) //9,8,7
}
let xx=arr3[Symbol.iterator](); //拿到遍历器对象
for(let i=0;i<arr3.length;i++){
    console.log(xx.next())
}
//Object {value: 9, done: false}
//Object {value: 8, done: false}
//Object {value: 7, done: false}
console.log(xx.next())
//Object {value: undefined, done: true}

It should be a bright spot, right? My idea is that for of traverses a data structure with an iterator interface, it will first execute the [Symbol.iterator] method, then return the iterator object (here, arr3 is used as an example to return arr3), and then call next of the iterator object (arr3) The method gets Object {value: 9, done: false}, and then calls the value attribute of the returned object to get the specific value.

6. Let’s look at a piece of code again to verify our idea and answer the first question: What is the basis for jumping out of the traversal process when for of traverses the data structure?
function Obj(value) {
    
    
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function() {
    
    
  var iterator = { next: next };

  var current = this;

  function next() {
    
    
    if (current) {
      var value = current.value;
      current = current.next;
      return { done: false, value: value };
    } else {
      return { done: true };
    }
  }
  return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;
for (var i of one){
  console.log(i); // 1, 2, 3
}

Similarly, let’s analyze this code. The for of loop is executed, and the [Symbol.iterator] method of the one object (actually, it should be called a pointer when referring to the js elevation) is called to get the iterator object iterator, and then the iterator object iterator is called. Next method, the first time it is executed, current is equal to the object pointed by the one pointer, so it will return { done: false, value: 1 }, and after execution, current = current.nextthis becomes the object pointed by the two pointer, and then the next traversal should traverse the two The object pointed by the pointer, when will it jump out? I believe you can also see that when current takes the next property of three, current is undefined, then { done: true } is returned. When for of gets { done: true }, it will jump out. Traversal, in order to verify my conjecture, I made the following modifications to the code

function Obj(value) {
    
    
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function() {
    
    
  var iterator = { next: next };

  var current = this;

  function next() {
    
    
    if (current) {
      var value = current.value;
      current = current.next;
      return { done: false, value: value };
    } else {
      return { done: false,value:"无限遍历"};
    }
  }
  return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;
for (var i of one){
  console.log(i); // 1, 2, 3
}

When I change {done:true} to {done: false,value:"infinite traversal"}, you will see that the for of loop cannot stop at all, and then the console will fill the screen with an "infinite loop" ” words, so the for of jumps out of the loop based on {done:true}

7. Going back to the original question, why can’t objects be traversed using for of?

The most direct answer should be that the object does not deploy the [Symbol.iterator] traversal interface, so why does it not deploy the traversal interface? Back to the definition again: The traversal interface is deployed so that the members of the data structure can be arranged in a certain order. When you look at the implementation of Array.prototype[Symbol.iterator]

Array.prototype[Symbol.iterator]=function(){
    
    
    this.nextIndex=0;
    this.next=function(){
    
    
        return this.nextIndex < this.length ?
        {value: this[this.nextIndex++], done: false} :
        {value: 1, done: true};
    }
    return this;
}

You may have noticed this.nextIndex, value:this[this.nextIndex++], which fully shows that the data interfaces for deploying traversal interfaces have linear rules, while the key values ​​​​of objects are arbitrary and disorganized, which is lost. The significance of deploying traversal interfaces

8. How to view the traversal interface of the data structure

The reason why the data structure can be traversed using for of is because the data structure deploys a traversal interface. When using for of for loop traversal, the traversal interface of the data to be traversed will be called by default. The traversal interface is generally a method of the data structure prototype. (For example: values, entries methods), let me teach you how to view the traversal interface of the data structure: view Symbol.iteratorthe methods corresponding to the attributes on the object prototype

const set = new Set([
  ['lc', 22],
  ['yx', 21]
]);
console.log(set);


The method corresponding to the attribute of the set structure Symbol.iteratoris the values ​​method, which means that when we call for of to traverse the set structure, it is equivalent to calling the values() method to get the traverser object (actually ===), that is set[Symbol.iterator]to set.valuessay There is no difference if you directly use the for of traversal set.values()method orset

for (const v of set){
  console.log(v); //["lc", 1],['yx', 21]
}
for (let v of set.values()){
  console.log(v)//["lc", 1],['yx', 21]
}
console.log(set[Symbol.iterator]===set.values) //true
9. I have something to say
let arr3=[9,8,7];
let xx=arr3[Symbol.iterator](); //拿到遍历器对象
console.log(xx) //[9, 8, 7, nextIndex: 0]

let arr4=[9,8,7];
let xxx=arr4[Symbol.iterator];
console.log(xxx()) //为什么这里就不能拿到上面带nextIndex的数组了呢

Welcome to leave a message and correct me, thank you very much! ! !

Supongo que te gusta

Origin blog.csdn.net/luo1055120207/article/details/75453247
Recomendado
Clasificación