1,什么是迭代器
每一个可迭代对象都对应着一个可迭代接口[Symbol.iterator];
[Symbol.iterator]接口并不是迭代器,他是一个迭代器工厂函数,调用该迭代接口即可返回一个待执行状态的迭代器;
不同的原生全局对象都对应着不同的迭代器;
const arr = new Array()
const map = new Map()
const set = new Set()
console.log(arr[Symbol.iterator]()) //Array Iterator?{}
console.log(map[Symbol.iterator]()) //MapIterator?{}
console.log(set[Symbol.iterator]()) //SetIterator?{}
将迭代器状态从待执行状态变为真正的执行:调用迭代器对象的 .next()方法;而其的返回值,就是next()方法的返回值对象:
const iterator = new Array(1, 2, 3, 4)[Symbol.iterator]()
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 4, done: false}
console.log(iterator.next()) //{value: undefined, done: true}
可以看到,当我执行第四次的时候,也就是对应着arr[3],但此时返回的对象中,done属性依旧是false,而执行第五次时,value变成了undefined,done变成了true,为什么会出现这种情况呢
在解答这个问题之前,我们需要在重新认识一下迭代器:
本质上来说,迭代器对象就是实现了next()方法的对象
const myIterator = {
next() {
if (length) {
return { value: 1, done: false }
} else {
return { value: undefined, done: true }
}
},
}
如上述,就是一个最简单的迭代器。调用next(),会执行迭代,返回done为false的迭代器生成对象,直到符合某种条件,返回done为true的迭代器生成对象。
你可以简单的把Array迭代器原理看作如下所示:
class MyArray extends Array {
[Symbol.iterator]() {
const { length } = this
const _this = this
let index = 0
return {
next() {
if (index < length) {
return { value: _this[index++], done: false }
} else {
return { value: undefined, done: true }
}
},
}
}
}
输出的结果也是一样的:
const iterator = new MyArray(1, 2, 3, 4)[Symbol.iterator]()
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 4, done: false}
console.log(iterator.next()) //{value: undefined, done: true}
那么,之前的问题就迎刃而解了。
或许你已经发现了,我自定义了一个类MyArray,并且我手动改写了他的迭代器接口。那么是否只要为某个不可迭代的对象,实现了[Symbol.iterator],就可以把它变成一个可迭代对象呢?
答案是肯定的。只要你想,你可以为任何对象加上可迭代协议,并把它变成可迭代对象。因为迭代对象的定义便是:实现迭代接口的对象。
2,自定义迭代接口
按照以上思路,我们就可以自己手动实现一个可迭代的Object对象了:
const prototype = {
[Symbol.iterator]() {
const entries = Object.entries(this)
const { length } = entries
let index = 0
return {
next() {
return index < length
? {
value: {
key: entries[index][0],
value: entries[index++][1],
},
done: false,
}
: { value: undefined, done: true }
},
}
},
}
const obj = Object.create(prototype)
obj.name = 'zhang san'
obj.age = 28
const objIterator = obj[Symbol.iterator]()
console.log(objIterator.next()) //{value: {key:'name',value:'zhang san'}, done: false}
console.log(objIterator.next()) //{value: {key:'age',value:28}, done: false}
console.log(objIterator.next()) //{value: undefined, done: true}
首先,我们声明了一个改写了迭代接口的对象,接着用Obejct.create()创建了以此对象为原型的obj。
该对象实例本身是没有迭代接口的,但是会沿着原型链去寻找prototype对象是否存在迭代接口。只要能在其原型链上找到迭代接口,那么就代表其是一个可迭代对象。如:
const objSon = Object.create(obj, {
name: {
enumerable: true,
writable: true,
value: 'zhang xiao san',
},
age: {
enumerable: true,
writable: false,
value: 2,
},
secret: {
enumerable: false,
writable: true,
value: 'secret',
},
})
const sonIterator = objSon[Symbol.iterator]()
console.log(sonIterator.next()) //{value: {key:'name',value:'zhang xiao san'}, done: false}
console.log(sonIterator.next()) //{value: {key:'age',value:2}, done: false}
console.log(sonIterator.next()) //{value: undefined, done: true}
objSon依旧是一个可迭代对象。
那么现在,我们就通过改造迭代接口[Symbol.iterator]的方式,把一个原本不是迭代类型的对象,变成了可迭代对象。
我们可以用该迭代接口来遍历任何enumerable的属性。但如果你想将enumrable为false的secret属性也遍历出来,那么只需要将迭代接口中的entries改造一下即可,一切皆由你想:
// const entries = Object.entries(this)
const ownKeys = Object.getOwnPropertyNames(this)
const entries = ownKeys.reduce(
(result, key) => [...result, [key, this[key]]],
[]
)
三,原生语言的迭代
以for - of 为例
for (const item of obj) {
console.log(item)
}
//{key:'name',value:'zhang san'}
//{key:'age',value:28}
for (const item of objSon) {
console.log(item)
}
//{key:'name',value:'zhang xiao san'}
//{key:'age',value:2}
可以看到,无论是obj,还是objSon,都可以正常用for of 循环,并且返回值为迭代器生成对象value属性的值。
你可以这么理解for - of 的机制:后台调用提供的可迭代对象的工厂函数[Symbol.iterator],从而创建一个迭代器,然后自动调用迭代器next执行。done为false,则将迭代器生成对象value赋值给item;done为true,则跳出循环:
const objIterator = obj[Symbol.iterator]()
{
const { value: item,done } = objIterator.next()
if(done) break
console.log(item) //{key:'name',value:'zhang san'}
}
{
const { value: item,done } = objIterator.next()
if(done) break
console.log(item) //{key:'age',value:28}
}
{
const { value: item,done } = objIterator.next()
if(done) break
console.log(item)
}
不仅仅是for - of,原生语言的迭代机制,都与此类似。数组解构,拓展操作符,Array.from,new Set(),new Map(),Promise.all(),Promise.race(),yield * 操作符等,都属于原生迭代语言。
了解了for - of 循环的机制之后,大家可以观察一下下面的例子:
const arr = [1, 2, 3, 4, 5, 6, 7, 8]
for (const item of arr) {
console.log(item)
if (item > 3) break
}
for (const item of arr) {
console.log(item)
}
//输出结果:1,2,3,4 | 1,2,3,4,5,6,7,8
const arrIterator = arr[Symbol.iterator]()
for (const item of arrIterator) {
console.log(item)
if (item > 3) break
}
for (const item of arrIterator) {
console.log(item)
}
//输出结果:1,2,3,4 | 5,6,7,8
循环arr的输出结果与循环arr迭代器arrIterator的结果明显的不同。为什么会出现这种现象呢?
在迭代器对象中,还有一个很重要的知识点:迭代器对象是一个一次性的,不可逆的对象。
因此,在迭代中某个地方终止,那么只能接着上一次终止的位置继续执行,而不会从头开始。
那么为什么对于arr本身使用for - of,却没有接着执行而是从头开始呢?可以回到介绍for - of 循环机制的那部分,其中有一句话:后台调用提供的可迭代对象的工厂函数[Symbol.iterator],从而创建一个迭代器。
也就是说,每调用一次for - of循环,都会创建一个新的迭代器对象,而该迭代器对象,在循环结束时就会被当作垃圾对象被回收。
虽然arr连续调用了两次for - of循环,但是在循环体的内部,并不是同一个迭代器对象。因此,即使上一个迭代器在item>3这个条件处中止了,但是下一次循环的迭代器对象,是一个全新的,还没有执行过的迭代器对象。
那么对于调用arr[Symbol.iterator]接口生成的的迭代器对象arrIterator,为什么会出现继续上次执行的情况呢,换句话说,为什么arrIterator的两次for循环,没有产生两次迭代器对象?
其实,迭代器对象本身,也实现了迭代器接口,也就是说。arr有一个迭代器接口[Symbol.iterator],而迭代器对象arrIterator也有一个迭代器接口[Symbol.iterator],并且,调用该迭代器对象,返回其本身:
console.log(arrIterator[Symbol.iterator]() === arrIterator) // true
所以虽然每次执行for-of循环都会同样的调用迭代器接口,但是该迭代器接口返回的对象就是arr的迭代器对象本身,而迭代器对象是一个一次性,不可逆的对象。因此,为什么会出现上述现象,也就显而易见了。
细心的朋友可能会想到一个问题:我们在之前手动实现的可迭代对象,其迭代器对象是否支持了迭代接口?是的,他不能。
for (const item of objIterator) {
console.log(item)
}
//Uncaught TypeError: objIterator is not iterable
但其实我们要优化也很简单,只要将该迭代器对象也实现一个迭代接口,并且该迭代接口工厂函数返回其本身,即可。
const prototype = {
[Symbol.iterator]() {
// const entries = Object.entries(this)
const ownKeys = Object.getOwnPropertyNames(this)
const entries = ownKeys.reduce(
(result, key) => [...result, [key, this[key]]],
[]
)
const { length } = entries
let index = 0
return {
next() {
return index < length
? {
value: {
key: entries[index][0],
value: entries[index++][1],
},
done: false,
}
: { value: undefined, done: true }
},
[Symbol.iterator]() {
return this
},
}
},
}
const objIterator = obj[Symbol.iterator]()
for (const item of objIterator) {
console.log(item)
if (item.key === 'name') break
}
for (const item of objIterator) {
console.log(item)
}
// {key: 'name', value: 'zhang san'}
// {key: 'age', value: 28}
现在,你可以用文中所介绍的方法,将任何对象变成一个可迭代对象。
文中内容均带有个人理解,并不保证权威。若有错误,欢迎随时指正。
|