探索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]遍历接口的实现逻辑呢?聪明的你可能已经想出来了

原生部署了遍历接口的数据结构有:Array、Map、Set、String、TypedArray、函数的 arguments 对象

如果你没有写出来Array.prototype[Symbol.iterator]遍历接口的实现,也不要灰心,跟着我接着往下看

4、一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可),不理解不要紧,看一下ES6标准人门中的代码

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
}

这里我们看下使用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;
}());

通过编译后的代码可以发现遍历器接口被加在了RangeIterator.prototype[Symbol.iterator]上,发现了什么?for of遍历range(0, 3)生成的对象,直接拿到值,是不是可以看成range(0,3)生成的对象直接调用了原型RangeIterator.prototype上的[Symbol.iterator]方法拿到了遍历器对象,然后再调用了原型上的next方法拿到{ done: false, value: value }对象,然后在取对象的value属性拿到实际的value值:0,1,2,for of是不是真的做了这些事呢?

5、我们来重写一下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;
}
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}

应该是眼前一亮吧?我的设想是for of遍历具有iterator接口的数据结构,会先执行[Symbol.iterator]方法,然后返回遍历器对象(这里以arr3为例就是返回arr3),然后调用遍历器对象(arr3)的next方法拿到Object {value: 9, done: false},然后调用返回对象的value属性拿到具体的值

6、我们再来看一段代码验证我们的构想,并回答开始的一个问题:for of遍历数据结构时是依据什么跳出遍历过程的?

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
}

同样的我们来分析这段代码,for of循环执行,调用one对象(其实引用js高程上的话说应该叫指针)的[Symbol.iterator]方法拿到遍历器对象iterator,然后调用遍历器对象iterator的next方法,第一次执行current等于one指针指向的对象,所以会返回{ done: false, value: 1 },并且执行 current = current.next 后this就变成了two指针指向的对象,然后接下来遍历就应该遍历two指针指向的对象了,什么时候跳出,相信你也看出来了当current取three的next属性时,current为undefined,则返回{ done: true },当for of拿到{ done: true }将会跳出遍历,为了验证我的猜想,我把代码做了如下修改

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
}

当我把{done:true}修改为{ done: false,value:"无限遍历"}时,你讲会看到这个for of循环根本停不下来了,接下来控制台就是满屏的"无限循环"字眼,所以for of跳出循环是依据{done:true}进行跳出的

7、回到最开始的问题,为什么对象不可以用for of进行遍历?

最直接的回到应该是对象没有部署[Symbol.iterator]遍历接口,那它为什么不部署遍历接口呢?再次回到定义:部署遍历接口是为了数据结构的成员能够按某种次序排列,当你看了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;
}

你可能会注意到了this.nextIndex,value:this[this.nextIndex++],这充分表明部署遍历接口的数据接口都是有线性规律的,而对象的键值都是任意的,杂乱无章的,这就失去了部署遍历接口的意义

8、如何查看数据结构的遍历接口

数据结构之所以可以用for of进行遍历,是因为数据结构部署了遍历接口,而用for of进行循环遍历时,会默认调用待遍历数据的遍历接口,遍历接口一般是数据结构原型的某一个方法(比如:values,entries方法),下面我来教你如何查看数据结构的遍历接口:查看对象原型上的 Symbol.iterator 属性对应的方法

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


set结构的 Symbol.iterator 属性对应的方法是values方法,这说明我们在调用for of对set结构进行遍历的时候,他相当于会调用values()方法拿到遍历器对象(其实 set[Symbol.iterator]=== set.values),也就是说你直接用for of遍历 set.values() 方法或者 set 是没有区别的

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、我有话说

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的数组了呢

欢迎大佬留言指正,万分感谢!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容