ES6:【深扒】 JavaScript 中的迭代器(1)

3. 手写实现 iterator 接口

function myIterator(arr) { let nextIndex = 0; return { next: function () { return nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, done: true } } } } let arr = [1,2,'ljc'] let it = myIterator(arr) console.log(it.next()); console.log(it.next()); console.log(it.next()); console.log(it.next()); console.log(it.next());

image-20210808230418267

我们来分析一下上面的代码,myIterator 函数是一个遍历器生成函数,它的作用是返回一个遍历器对象,也是指针对象。

我们通过 next 方法来移动指针,next 方法内部通过闭包来保存指针nextIndex的值,每次调用 next 方法 nextIndex都会 +1 ,然后根据nextIndex 的值从数组内取出数据作为 value ,通过索引判断得到 done ,当无数据可用时,超过数组最大索引,无可用数据返回,此时 donetrue

可迭代对象


了解过了 iterator,并且我们也已经知道了如何创建一个遍历器对象,但是这和我们先前所说的 for...of 循环有什么关系呢

这里首先我们需要了解一下 for...of运行机制

for...of循环执行时,循环内部会自动调用这个对象上的迭代器方法Symbol.iterator , 依次执行迭代器对象的 next 方法,将 next 方法的返回值赋值给 for ...of 内的变量,从而得到具体的值,实现遍历。

1. 手写实现可迭代对象

一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”。

Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数,执行这个函数,就会返回一个迭代器对象

也就是说要实现可迭代对象只要在对象上部署Symbol.iterator属性,为它创建一个迭代器方法就可以了

let iteratorObj = { items: [1, 2, 'ljc'], // 部署Symbol.iterator属性 [Symbol.iterator]: function () { let self = this let i = 0 return { next: function () { // 类型转化为Boolean let done = (i >= self.items.length) // 数据确认 let value = !done ? self.items[i++] : undefined // 数据返回 return { done, value } } } } } for (let item of iteratorObj) { console.log(item); // 1 2 ljc }

显然实现了 iterator 接口,可以被 for...of 成功遍历

2. Iterator 原生应用场景

有些对象我们并没有为它们部署 Iterator 接口,但是仍然可以使用 for...of 进行遍历。这是因为在ES6中有些对象已经默认部署了这个接口。

原生具备 Iterator 接口的数据结构:

  • Array

  • set容器

  • map容器

  • String

  • 函数的 arguments 对象

  • NodeList 对象

Array

在数组上成功的找到了 Symbol.iterator 方法,并能够执行返回迭代器对象,同时验证了for...of循环成功执行

let arr = [1, 2, 3] let it = arr[Symbol.iterator]()//返回迭代器对象 console.log(it.next()); console.log(it.next()); console.log(it.next()); console.log(it.next()); for (let item of arr) { console.log(item); }

image-20210809001450172

其余几种都一个套路,不多说

Q&A

看到这里你可能会想,为什么这么多数据结构都实现了默认部署,为什么偏偏对象没有呢?

当然是有原因的

对象可能有各种各样的属性,不像数组的值是有序的,所以对对象遍历时根本不知道如何确定先后顺序,所以需要我们手动实现

提前退出循环


普通的 for 循环是可以随时中断的,for...of 循环作为 forforEach 的升级版同样是可以的

迭代器对象除了有 next 方法,还有两个可选方法 return 方法和 throw 方法

return 方法的使用场景是,当 for...of 循环提前退出,就会调用 return 方法。

需要特别注意的是,return 方法必须有一个 object 类型的返回值

我们在前面代码的基础上添加上 return 方法,并在 for...of 循环中采用 break 语句来中断循环,循环提前退出,自动调用 return 方法输出提前退出

let iteratorObj = { items: [1, 2, 'ljc'], // 部署Symbol.iterator属性 [Symbol.iterator]: function () { let self = this let i = 0 return { next: function () { // 类型转化为Boolean let done = (i >= self.items.length) // 数据确认 let value = !done ? self.items[i++] : undefined // 数据返回 return { done, value } }, return () { console.log('提前退出'); return { done: true } } } } } for (let item of iteratorObj) { console.log(item); // 1 break; }

注意

如果采用抛出异常的方式退出,会先执行 return 方法再抛出异常

关于 throw 方法会在下篇生成器文章中提到

Iterator 接口使用场景


除了 for...of 循环会自动调用 iterator 接口之外,还有几个场景也会自动调用

1. 解构赋值

对可迭代对象进行解构赋值时,会默认调用 Symbol.iterator 方法

let map = new Set().add('a').add('b'); let [x, y] = map console.log(x, y, map) // a b Set(2) {"a", "b"}

由于解构赋值适用于可迭代对象,那么我们对自己自定义的可迭代对象解构赋值试试

let iteratorObj = { items: [1, 2, 'ljc'], // 部署Symbol.iterator属性 [Symbol.iterator]: function () { let self = this let i = 0 return { next: function () { // 类型转化为Boolean let done = (i >= self.items.length) // 数据确认 let value = !done ? self.items[i++] : undefined // 数据返回 return { done, value } } } } } let [a, b, c] = iteratorObj console.log(a, b, c)// 1 2 'ljc'

成功的实现了解构赋值

2. 扩展运算符

扩展运算符也会默认调用Symbol.iterator方法,可以将当前数据结构转化为数组

// 阮老师的例子 var str = 'hello'; [...str] // ['h','e','l','l','o'] let arr = ['b', 'c']; ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']

3. yield*

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

let generator = function* () { yield 1; yield* [2,3,4]; yield 5; }

这里并不是它的主战场,下节说明

总结


ES6 中新增了新的数据结构,为了提供一种统一的遍历方法,新增了 for...of 方式。而 for...of 执行的时候会自动调用迭代器来取值

只有实现了 Iterator 接口的对象才能采用 for...of

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。

l7THz-1713485292579)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-6zRvXig1-1713485292579)]

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。

[外链图片转存中…(img-ttBnt7uH-1713485292580)]

  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
该文档是根据博客园汤姆大叔的入理解JavaScript系列(http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html)博文整理而成,主要内容包括: 1.编写高质量JavaScript代码的基本要点 2.揭秘命名函数表达式 3.全面解析Module模式 4.立即调用的函数表达式 5.强大的原型和原型链 6.S.O.L.I.D五大原则之单一职责SRP 7.S.O.L.I.D五大原则之开闭原则OCP 8.S.O.L.I.D五大原则之里氏替换原则LSP 9.根本没有“JSON对象”这回事! 10.JavaScript核心(晋级高手必读篇) 11.执行上下文(Execution Contexts) 12.变量对象(Variable Object) 13.This? Yes,this! 14.作用域链(Scope Chain) 15.函数(Functions) 16.闭包(Closures) 17.面向对象编程之一般理论 18.面向对象编程之ECMAScript实现 19.求值策略 20.《你真懂JavaScript吗?》答案详解 21.S.O.L.I.D五大原则之接口隔离原则ISP 22.S.O.L.I.D五大原则之依赖倒置原则DIP 23.JavaScript与DOM(上)——也适用于新手 24.JavaScript与DOM(下) 25.设计模式之单例模式 26.设计模式之构造函数模式 27.设计模式之建造者模式 28.设计模式之工厂模式 29.设计模式之装饰者模式 30.设计模式之外观模式 31.设计模式之代理模式 32.设计模式之观察者模式 33.设计模式之策略模式 34.设计模式之命令模式 35.设计模式之迭代器模式 36.设计模式之介者模式 37.设计模式之享元模式 38.设计模式之职责链模式 39.设计模式之适配器模式 40.设计模式之组合模式 41.设计模式之模板方法 42.设计模式之原型模式 43.设计模式之状态模式 44.设计模式之桥接模式 45.代码复用模式(避免篇) 46.代码复用模式(推荐篇) 47.对象创建模式(上篇) 48.对象创建模式(下篇)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值