为什么要有Iterator
- 它是一种接口机制,为各种不同的数据结构提供统一的访问机制
- 让不支持遍历的数据结构“可遍历”,主要供
for ... of
消费
基础使用
它是一种接口,这个接口要求return一个对象,并且这个对象要有next函数,而且next函数需要return一个对象,这个对象要有value属性和down属性
例1:组的遍历(数组本身就可以遍历)
function makeIterator(arr) {
let nextIndex = 0
return {
next() {
return nextIndex < arr.length ? {
value: arr[nextIndex++],
down: false
} : {
value: false,
down: true
}
}
}
}
makeIterator(['a', 'b', 'c'])
let it = makeIterator(['a', 'b', 'c'])
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
从上面的代码可以看出:结构和Generator很像,因为Generator自带了next功能
例2: 遍历不可遍历的对象
如果遍历不可遍历的对象会怎样
let courses = {
allCourse: {
frontend:['ES6','Vue','React','小程序'],
backend: ['Java','Go','Python','Spring boot'],
webapp: ['Android','ios']
}
}
for(let item of courses) {
console.log(item);
}
迭代不可迭代实例的尝试无效。
为了可迭代,非数组对象必须具有[Symbol.iterator]()
方法。
如果让当前的对象拥有Symbol.iterator
方法是不是就可以变成可迭代(可遍历)的了呢?
数组中有 Symbol.iterator 吗?
既然一个对象如果要是可迭代的,就需要有Symbol.iterator
方法
,那么我们知道数组是可迭代的,我们不妨看下它是否有这个方法呢?
let arr = ['a','b','c']
console.log(arr);
因此只要对象下有Symbol.iterator
属性就表示它是可遍历的或者说可迭代的,那我们是不是也可以通过next()
来输出里面的值呢?
let arr = ['a','b','c']
console.log(arr);
let it = arr[Symbol.iterator]()
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())
也就说对于数组这种可迭代的结构,它自带了Symbol.iterator
,也就是说它遵循了Iterator协议
Map中有Symbol.iterator 吗?
let map = new Map()
map.set('name','es6')
map.set('age','18')
map.set('school','Beida')
console.log(map);
我们发现Map结构的数据也带了Symbol.iterator
,说明它也是可遍历或者说可迭代的对象
既然说可迭代的对象,那我们也可以使用next()
遍历里面的属性
let map = new Map()
map.set('name','es6')
map.set('age','18')
map.set('school','Beida')
let it = map[Symbol.iterator]()
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
因此,Map也具有Iterrator这种接口的数据结构
我们之前学过哪些具备Iterator接口的数据结构呢?
- Array
- Map
- Set
- String
- 函数的arguments对象
- NodeList对象
两个协议
- 可迭代协议:当前对象上是否有
[Symbol.iterator]
属性,如果有就表示是可迭代的,使用for ... of
进行遍历,反之则表示不可迭代 - 迭代器协议:当前的迭代器必须符合下面这种结构:
return {
next() {
return {
done,
value
}
}
}
让我们套用上面两个协议,让不可遍历的对象变成可迭代吧!
let courses = {
allCourse: {
frontend:['ES6','Vue','React','小程序'],
backend: ['Java','Go','Python','Spring boot'],
webapp: ['Android','ios']
}
}
将上面不可以迭代的对象,按照上面两个协议进行改写:
let courses = {
allCourse: {
frontend:['ES6','Vue','React','小程序'],
backend: ['Java','Go','Python','Spring boot'],
webapp: ['Android','ios']
}
}
courses[Symbol.iterator] = function() {
let allCourse = this.allCourse
let keys = Reflect.ownKeys(allCourse)
let values = []
return {
next() {
// 保证values是空数组
if(!values.length){
// 保证 allCourse 中还有属性
if(keys.length) {
// 每次只取第一个属性,这样能保证每次取的都不一样
values = allCourse[keys[0]]
// 取完就删除第一个属性,删除数组第一个元素,并返回删除的结果
keys.shift()
}
}
return {
// vlues中没有值的时候说明allCourse的属性被取完了
done: !values.length,
// 每次next的只取第一个
value: values.shift()
}
}
}
}
for(let item of courses) {
console.log(item);
}
由于当前既遵循了可迭代协议,又遵循了迭代器协议,因此我们就将不可迭代的对象变成可迭代的对象
这样好像很麻烦啊!那么我们为什么不先取frontend
,再取backend
,再取webapp
最后将数组拼接上呢?
我们不妨设想这样一个场景: 如果一个项目是多人合作的,碰到一个类似上面的对象,如果团队下面很多人都需要在不同的模块或业务中遍历这个对象中的值,那么岂不是我们每个人都要实现一遍,一个个取值,然后拼接到一起
如果我们把这个对象做成可迭代的对象,那么其他人如果想要遍历这个对象只需要使用for... of
遍历即可,这样岂不是方便自己也方便他人?
使用Generator
的方式实现对象的Iterator
之前学的Generator
也是返回一个对象,这个对象也是有next
方法,并且next方法的返回值也是value
和done
,因此我们可以使用Iterator
这个迭代器中使用Generator
这样我们就不需要自己去定义next
方法
Generator函数有两个特点:
function
后加型号*
- 里面用到
yield
表达式
let courses = {
allCourse: {
frontend: ['ES6', 'Vue', 'React', '小程序'],
backend: ['Java', 'Go', 'Python', 'Spring boot'],
webapp: ['Android', 'ios']
}
}
courses[Symbol.iterator] = function * () {
let allCourse = this.allCourse
let keys = Reflect.ownKeys(allCourse)
let values = []
while (true) {
// 如果values是空的并且allCourse中有属性那么就存
if (!values.length) {
if (keys.length) {
values = allCourse[keys[0]]
keys.shift()
yield values.shift()
} else {
return false
}
// 如果values不是空的就输出被删除的第一个元素
} else {
yield values.shift()
}
}
}
for(let item of courses) {
console.log(item);
}
由于Generator中自己实现了next
方法,已经具备了value
和done
,因此就不需要我们自己重写next方法,也不需要返回具有value
和done
的对象
因此,当我们想用Iterator接口重写对象,让不可遍历的对象变成可遍历的对象的时候,我们就要考虑下是要自己实现一个next方法,还是借助Generator
帮助我们取实现一个next
总结:为什么说Iterrator是一个接口机制?
对任何的数据结构,虽然当前不可以循环遍历,但是只要它符合可迭代协议和迭代器协议,那么它就可以变成可迭代的,我们就可以使用
for ... of
的方式对这个结构进行迭代(遍历)。这样任何数据结构都变成可遍历的,可遍历的概念就变得更加的广义了
原文链接:https://juejin.cn/post/7235091963312996413 作者:仗剑走天涯_