迭代器与生成器
迭代器 Iterator
概念
从一个数据集合中按照一定的顺序,不断取出数据的过程。
迭代和遍历的区别:迭代强调的是以此取数据,不保证取多少,遍历强调的是要把整个数据依次全部取出。
可迭代协议
迭代器:一个具有 next 方法的对象,next 方法返回下一个数据并且能指示是否迭代完成
ES6 规定,如果一个对象具有知名符号属性Symbol.iterator
,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable
) 数组就是个可迭代对象
对象和数组中的部分遍历方法是通过迭代器实现的
遍历
js 对象中支持以下方式的遍历
遍历数组
for
循环forEach
map
reduce
keys
values
for of
......
其中keys
, values
for of
需要Iterator
支持
遍历 Map/Set
keys
entries
forEach
......
遍历 Object
for in
- 先
Object.keys(obj)
得到对象每个属性的数组, 然后使用数组的遍历方法遍历每个key
,就能获取每个key
对应的value
Iterator 和 for of
Iterator
是ES6
提出的一个接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要包含Iterator
接口,就可以完成遍历操作。
Iterator 的作用
为各种数据结构,提供一个统一的、简便的访问接口。
ES6 提出了新的遍历命令for...of
循环,Iterator
接口主要供 for...of
消费。
Iterator 的遍历过程
既然数组是支持for...of
循环的,那数组肯定包含了Iterator
接口,我们通过它来看看Iterator
的遍历过程。
const arr = [1, 2, 3, 4, 5]
const iterator = arr[Symbol.iterator]()
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
const arr = [1, 2, 3, 4, 5]
const iterator = arr[Symbol.iterator]()
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
从图中我们能看出:
Iterator
接口返回了一个有next
方法的对象。每调用一次
next
,依次返回了数组中的项,直到它指向数据结构的结束位置。返回的结果是一个对象,对象中包含了当前值
value
和当前是否结束done
用 for of 遍历 Object
let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
console.log(value)
}
let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
console.log(value)
}
以上代码运行会报错,原因就是 Object
原型链上没有 iterator
方法,我们可以手动实现一个 iterator 方法让 Object
支持迭代器,这样就可以实现 Object
的for of
function objectIterator() {
const keys = Object.keys(this)
let index = 0
return {
// 实现了 next 方法,满足成为迭代器的条件,则该[Symbol.iterator]方法的返回值是一个迭代器
next: () => {
const done = index >= keys.length
const value = done ? undefined : this[keys[index]]
index++
return {
value,
done
}
},
// 迭代器提前关闭执行的操作
return() {
console.log('提前关闭!')
return { done: true }
}
}
}
Object.prototype[Symbol.iterator] = objectIterator
let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
console.log(value)
}
// 1
// 2
// 3
function objectIterator() {
const keys = Object.keys(this)
let index = 0
return {
// 实现了 next 方法,满足成为迭代器的条件,则该[Symbol.iterator]方法的返回值是一个迭代器
next: () => {
const done = index >= keys.length
const value = done ? undefined : this[keys[index]]
index++
return {
value,
done
}
},
// 迭代器提前关闭执行的操作
return() {
console.log('提前关闭!')
return { done: true }
}
}
}
Object.prototype[Symbol.iterator] = objectIterator
let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
console.log(value)
}
// 1
// 2
// 3
生成器 Generator
概念
生成器是一个通过构造函数Generator
创建的对象,生成器既是一个迭代器(有 next
方法),同时又是一个可迭代对象(有知名符号),可以用于 for of
循环。
//伪代码,由JS引擎内部使用
var generator = new Generator()
generator.next() //它具有next方法
var iterator = generator[Symbol.iterator] //它也是一个可迭代对象
for (const item of generator) {
//由于它是一个可迭代对象,因此也可以使用for of循环
}
//伪代码,由JS引擎内部使用
var generator = new Generator()
generator.next() //它具有next方法
var iterator = generator[Symbol.iterator] //它也是一个可迭代对象
for (const item of generator) {
//由于它是一个可迭代对象,因此也可以使用for of循环
}
生成器函数的书写
// 使用这种语法定义的函数,执行后返回一个生成器
function* method {}
// 使用这种语法定义的函数,执行后返回一个生成器
function* method {}
运行过程
生成器函数的运行过程如下
第一次调用:生成了生成器,并没有调用内部逻辑
后续调用:每次调用生成器的
next
方法,将导致生成器函数运行到下一个 yield 关键字的位置。yield 关键字的执行表示产生了一条迭代数据。
function* generator() {
let a = yield 1
let b = yield 2
let c = yield 3
console.log(`a = ${a}, b = ${b}, c = ${c}`)
}
let gen = generator() // "Generator { }"
console.log(gen.next(10)) // {value: 1, done: false}
console.log(gen.next(9)) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
console.log(gen.next(8))
// a = 9, b = undefined, c = 8
// {value: undefined, done: true}
function* generator() {
let a = yield 1
let b = yield 2
let c = yield 3
console.log(`a = ${a}, b = ${b}, c = ${c}`)
}
let gen = generator() // "Generator { }"
console.log(gen.next(10)) // {value: 1, done: false}
console.log(gen.next(9)) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
console.log(gen.next(8))
// a = 9, b = undefined, c = 8
// {value: undefined, done: true}
从上述代码可以看出:
next()
的参数会传给上一条执行的yield
语句左边的变量。 所以,第一次调用gen.next(10)
,参数 10 并没有传递给任何变量。
第二次调用gen.next(9)
时,参数 9 传递给了上一条执行的yield
语句左边的变量a
第三次调用gen.next()
时,没有传递参数,所以变量b
的值为undefined
第四次调用gen.next(8)
时,虽然返回值中done: true
,但也不影响参数的传递
此外,next()
函数的返回值,与传递的参数无关,只与yield
右边返回的值以及生成器是否结束有关。
yield 关键字和 yield*表达式
yield
关键字用来暂停和恢复一个生成器函数
yield*
表达式用于委托给另一个 generator 或可迭代对象
生成器与 async/await 的实现
async function getResult() {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
console.log(1)
}, 1000)
})
console.log(2)
}
async function getResult() {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
console.log(1)
}, 1000)
})
console.log(2)
}
function* getResult() {
yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
console.log(1)
}, 1000)
})
console.log(2)
}
const gen = getResult()
function co(g) {
g.next().value.then(() => {
co(g)
})
}
co(gen)
function* getResult() {
yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
console.log(1)
}, 1000)
})
console.log(2)
}
const gen = getResult()
function co(g) {
g.next().value.then(() => {
co(g)
})
}
co(gen)
通过生成器对比两个字符串是否相等
function* walk(str) {
let part = ''
for (let i = 0; i < str.length; i++) {
part += str[i]
yield part
}
}
function compare(s1, s2) {
let genS1 = walk(s1)
let genS2 = walk(s2)
while (true) {
const n1 = genS1.next()
const n2 = genS2.next()
if (n1.value !== n2.value) {
return false
}
if (n1.done && n2.done) {
return true
}
}
}
const s1 = '1234567777'
const s2 = '1234567777'
console.log(compare(s1, s2))
function* walk(str) {
let part = ''
for (let i = 0; i < str.length; i++) {
part += str[i]
yield part
}
}
function compare(s1, s2) {
let genS1 = walk(s1)
let genS2 = walk(s2)
while (true) {
const n1 = genS1.next()
const n2 = genS2.next()
if (n1.value !== n2.value) {
return false
}
if (n1.done && n2.done) {
return true
}
}
}
const s1 = '1234567777'
const s2 = '1234567777'
console.log(compare(s1, s2))