迭代器和生成器
参考高级程序设计4
迭代器
for循环是一种最简单的迭代,循环是迭代机制的基础,循环可以指定迭代的次数,迭代在一个有序集合上进行,如数组。
数组有已知长度且可以通过索引获取,所以在循环的时候可以通过递增的方式来遍历,因为以下几个原因循环来执行例程并不理想。
- 迭代之前需要知道数据结构,取值的时候只能通过[]来获取,这种方式并不适合所有的数据结构
- 遍历顺序并不是数据结构固有的,并不适合其他具有隐式顺序的数据结构
es5新增高阶函数forEach就是为了解决这个问题而诞生的,但是它不能识别何时终止迭代
迭代器模式和可迭代协议
- 把一些些结构称为“可迭代对象”(iterable),因为它们实现了正式的 Iterable 接口
- 任何实现 Iterable(可迭代协议) 接口的数据结构都可以被实现 Iterator 接口的结构
- 很多数据都内置了Iterator 接口如字符串、数组、映射、nodeList,arguments,
let str = 'abc'
let arr = ['a', 'b', 'c']
console.log(str[Symbol.iterator])
console.log(arr[Symbol.iterator])
// f values() { [native code] }
// f values() { [native code] }
迭代器协议
可以通过next()调用迭代器,返回一个 IteratorResult 对象。IteratorResult中有done和value两个属性,done返回布尔值来确定后面是否还有值,done为true时表示状态耗尽,不能再次迭代。value返回对应的当前值
const arr = [1, 2]
const iter = arr[Symbol.iterator]()
console.log(iter.next()); // { done: false, value: 1 }
console.log(iter.next()); // { done: false, value: 2 }
console.log(iter.next()); // { done: true, value: undefined }
每个迭代器都表示对可迭代对象的一次性有序遍历。不同迭代器的实例相互之间没有联系,只会独
立地遍历可迭代对象
const arr = [1, 2]
const iter = arr[Symbol.iterator]()
const iter2 = arr[Symbol.iterator]()
console.log(iter.next()); // { done: false, value: 1 }
console.log(iter.next()); // { done: false, value: 2 }
console.log(iter2.next()); // { done: false, value: 1 }
console.log(iter2.next()); // { done: false, value: 2 }
自定义迭代器
class Counter {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let count = 1,
limit = this.limit;
return {
next() {
if (count <= limit) {
return { done: false, value: count++ };
} else {
return { done: true, value: undefined };
}
},
return () {
return { done: true };
}
};
}
}
const counter = new Counter(3)
for (const item of counter) {
if (item > 2) {
continue
}
console.log(item) // 1, 2
}
生成器
- 生成器可以在函数中,暂停和恢复代码执行的能力
- 在函数名的前面加一个“ * ”代表生成器,在生成器实现的时候一开始是暂停状态,当调用next的时候才会恢复执行
- yield之前的代码会正常执行,遇到yield会暂停,函数中的作用域会的状态会被保留,当调用next才会调用yield之后的代码
// return和yield同样的效果,但是yield能写多个, 如果有多个return那么后面的代码都不会执行,只会执行第一个return
function* generatorFn(opt) {
yield opt;
yield '1';
return '2';
return '3';
}
//
const fn = generatorFn('123')
console.log(fn.next(), fn.next(), fn.next(), fn.next())
// {value: '123', done: false}
// {value: '1', done: false}
// {value: '2', done: true}
// {value: undefined, done: true}
console.log(bbb.next())
// {value: '123', done: false}
生成器作为可迭代对象
function* nTimes(n) {
while(n--) {
yield n;
}
}
for (const i of nTimes(3)) {
console.log(i) // 直接得到value, 返回的不是{ done,value }
}
function* generatorFn (opt) {
console.log(yield) // 作为函数中间参数使用
console.log(yield)
console.log(yield)
}
// 如果 yield 在第一行 后面没有东西返回0,其他的yield后面没有东西返回undefined
const generfn = generatorFn()
generfn.next() // 0
generfn.next() // undefined
generfn.next() // undefined
function* generator (opt) {
console.log(opt)
console.log(yield)
console.log(yield)
}
const gener = generator(123)
gener.next(321) // 123, 因为正在调用生成器
gener.next(456) // 456
gener.next(789) // 789
yield 关键字可以同时用于输入和输出
const gener = generator()
console.log(gener.next(), gener.next(456))
// {value: '123', done: false}
// {value: 456, done: true}
// 函数要对表达式求值才能确定返回的值,当遇到yield时并计算值123,
// 下一次调用的时候传入了456,交给同一个yield处理,
// 然后456被确定为生成器函数产出的值
通过“ * ”加强yield
function* generatorFn2 () {
yield* [1, 2, 3]
// 等价于
// for (const i of [1, 2, 3]) {
// yield i
// }
}
console.log(Array.from(generatorFn2())) // [1, 2, 3]
通过yield*调用生成器
function* innerFn() {
yield 'foo';
return 'bar';
}
function* outerFn(genObj) {
console.log('iter value:', yield* innerFn());
}
for (const x of outerFn()) {
console.log('value:', x);
}
生成器作为默认迭代器
// 生成器函数 产生一个生成器函数
class Foo {
constructor (opt) {
this.opt = opt
}
* [Symbol.iterator] () {
yield* this.opt
}
}
for (const key of new Foo([1, 2, 3])) {
console.log(key)
}
终止生成器
使用throw(), return() 终止生成器
// 所有的生成器都有 return() 会提前进入结束状态, 后续调用都是 done: true
function * Fn () {
for (const item of [1, 2, 3]) {
yield item
}
}
const fn = Fn()
console.log(fn.next()) // {value: 1, done: false}
console.log(fn.return(666)) // {value: 666, done: true} value默认undefined
function* Fn2() {
for (const x of [1, 2, 3]) {
yield x
}
}
const g = Fn2()
for (const x of g) {
if (x > 1) {
g.return(4);
}
console.log(x); // 1, 2
}
// 通过throw 来调用会抛出一个错误,如果处理这个错误会继续往下面执行
function* Fn2() {
yield* [1, 2, 3]
}
const fn2 = Fn2()
try {
fn2.throw('困')
} catch (err) {
console.log(err) // 困
}
// 如果在生成器内部处理这个错误,那么调用throw时会跳过对应的这一项,如果throw放在所有next前面,也不会执行后面的
function* Fn3() {
for (const item of [1, 2, 3]) {
try {
yield item
} catch {}
}
}
const fn3 = Fn3()
console.log(fn3.next()) // 1
fn3.throw('困') // yield抛出一个错误,不会在产出2
console.log(fn3.next()) // 3