这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
一、什么是迭代器
本质: 一个可以不断从中取值(直到取值结束)的对象。
迭代器从使用者的角度来看,我们无需关心值的计算方式,只需要使用即可。
要求: 满足迭代器协议。
- 这个对象提供一个next函数
- next函数的调用结果为返回一个对象
{done:bool, value: any}
1. 基本实现
let index = 0
const bears = ['ice', 'panda', 'grizzly']
let iterator = {
next() {
if (index < bears.length) {
return { done: false, value: bears[index++] }
}
return { done: true, value: undefined }
}
}
console.log(iterator.next()) //{ done: false, value: 'ice' }
console.log(iterator.next()) //{ done: false, value: 'panda' }
console.log(iterator.next()) //{ done: false, value: 'grizzly' }
console.log(iterator.next()) //{ done: true, value: undefined }
上方的iterator对象符合迭代器协议,所以是一个迭代器。
但是代码违背了高内聚的思想 index和 iterator对象从功能上看属于一个整体,但是却使用了全局变量,当迭代器数量多的时候,考虑到复用性,需要进行封装。
2. 封装实现
const bears = ['ice', 'panda', 'grizzly']
function createArrIterator(arr) {
let index = 0
let _iterator = {
next() {
if (index < arr.length) {
return { done: false, value: arr[index++] }
}
return { done: true, value: undefined }
}
}
return _iterator
}
let iter = createArrIterator(bears)
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
上方代码内聚性非常高,尽可能的进行了复用。
二、什么是可迭代对象
首先 迭代器 和 可迭代对象 不是一个东西 但是存在关联。
可迭代对象是是一个符合可迭代对象协议的对象。
可迭代对象协议
-
实现类[Symbol.iterator]作为对象中的key的方法,且 这个方法返回一个迭代器对。
-
for of的本质就是调用[Symbol.iterator]为key的方法
Js内置可迭代对象 String、Array、Set、NodeList(类数组对象)、Argument(类数组对象)
示例程序
可迭代对象实现
let info = {
bears: ['ice', 'panda', 'grizzly'],
[Symbol.iterator]: function() {
let index = 0
let _iterator = {
//这里一定要箭头函数,或者手动保存上层作用域的this
next: () => {
if (index < this.bears.length) {
return { done: false, value: this.bears[index++] }
}
return { done: true, value: undefined }
}
}
return _iterator
}
}
let iter = info[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
//符合可迭代对象协议 就可以利用 for of 遍历
for (let bear of info) {
console.log(bear)
}
//ice panda grizzly
可迭代对象的应用
- for of
- 展开语法
- 解构语法
- promise.all(iterable)
- promise.race(iterable)
- Array.from(iterable)
- …
自定义类迭代实现
class myInfo {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
[Symbol.iterator]() {
let index = 0
let _iterator = {
next: () => {
const friends = this.friends
if (index < friends.length) {
return {done: false, value: friends[index++]}
}
return {done: true, value: undefined}
}
}
return _iterator
}
}
const info = new myInfo('ice', 22, ['panda','grizzly'])
for (let bear of info) {
console.log(bear)
}
//panda
//grizzly
上方代码实现使用 for of 对 friends 进行了迭代
三、生成器函数
1. 生成器认识
生成器是ES6新增的 函数控制 方案,能够控制函数的暂停和继续。
- 生成器函数定义使用
function* - 通过
yield控制函数的执行 - 返回一个
Generator,Generator是一个特殊的迭代器
function* bar() {
console.log('fn run')
}
bar()
上方代码运行后,生成器函数bar没有执行,其实是暂停了,Generator是一个特殊的迭代器,所以有next方法。
function* bar() {
console.log('fn run')
}
const generator = bar()
console.log(generator.next())
//fn run
//{ value: undefined, done: true }
调用next方法就能进行一次执行。
function* bar() {
console.log('fn run start')
yield 100
console.log('fn run...')
yield 200
console.log('fn run end')
return 300
}
const generator = bar()
//1. 执行到第一个yield,暂停之后,并且把yield的返回值 传入到value中
console.log(generator.next())
//2. 执行到第一个yield,暂停之后,并且把yield的返回值 传入到value中
console.log(generator.next())
//3. 执行剩余代码
console.log(generator.next())
//打印结果:
//fn run start
//{done:false, value: 100}
//fn run...
//{done:false, value: 200}
//fn run end
//{done:true, value: 300}
上方代码是生成器的多次执行。
- 调用next方法时,代码就会开始执行
- 执行到yield x 后,就会暂停,等下下一次调用next方法,函数就会继续往下执行,如果没有了yield关键字,最后一次的next返回的 done为true。
2.生成器函数分段传参
生成器函数可以分段执行也可以分段传参。
function* bar(nickName) {
const str1 = yield nickName
const str2 = yield str1 + nickName
return str2 + str1 + nickName
}
const generator = bar('ice')
console.log(generator.next())
console.log(generator.next('panda '))
console.log(generator.next('grizzly '))
console.log(generator.next())
// { value: 'ice', done: false }
// { value: 'panda ice', done: false }
// { value: 'grizzly panda ice', done: true }
// { value: undefined, done: true }
解释
- yield左侧可以接受参数
- 参数来源于调用next方传入的实参
注意: 并不是所有的实参都能传入迭代器方法内部。
如上方两个图,第一次调用next方法传入的值不会被接收,第一次其接收的值是生成器函数执行传入的值
在生成器函数执行完最后一个yield语句,暂停之后,后方还有代码(比如使用return返回)的时候,再次调用next,其实参也传入不了内部,因为没有yield关键字可以接收参数了。
3. 生成器替代迭代器
因为生成器是一个特殊的迭代器,所以生成器是可以代替迭代器的
let bears = ['ice','panda','grizzly']
function* createArrIterator(bears) {
for (let bear of bears) {
yield bear
}
}
const generator = createArrIterator(bears)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
yield*语法糖写法
• yield* 依次迭代这个可迭代对象,相当于遍历拿出每一项 yield item(伪代码)
let bears = ['ice','panda','grizzly']
function* createArrIterator(bears) {
yield* bears
}
const generator = createArrIterator(bears)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
• 依次迭代这个可迭代对象,返回每个item值
四、可迭代对象终极(优雅)封装示例
class myInfo {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
*[Symbol.iterator]() {
yield* this.friends
}
}
const info = new myInfo('ice', 22, ['panda','grizzly'])
for (let bear of info) {
console.log(bear)
}
//panda
//grizzly
本篇文章是为了强化对知识的学习而写的文章。
原作者:一只小ice