Iterator(迭代器)
查看
迭代器:使用户在容器对象(如数组)上遍访的对象。即迭代器是帮助我们对某个数据结构进行遍历的对象。
在JS中,迭代器是一个具体的对象,我们可以手动实现,但这个对象需要符合迭代器协议(iterator protocol)
next方法有如下要求:
实现迭代器,以获取对象的键(包含Symbol)为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function createObjIterator(obj) { let keys = Reflect.ownKeys(obj) let index = 0 return { next() { if (index >= keys.length) { return { done: true } }
return { done: false, value: keys[index++] } } } }
const obj = { name: '111', age: 12, [Symbol()]: 222 }
const objIterator = createObjIterator(obj) console.log(objIterator.next()) console.log(objIterator.next()) console.log(objIterator.next()) console.log(objIterator.next())
|
可迭代对象
查看
可迭代对象和迭代器是两个不同的概念,前面我们可以通过迭代器对对象进行遍历操作,但需遍历对象和迭代器时分离开的,如果我们把迭代器和对象结合在一起,就叫做可迭代对象。
但将迭代器和可迭代对象结合起来成为可迭代对象,可是有一定规范的:
可迭代对象是可以通过for of 循环进行遍历操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const obj = { name: '111', age: 12, [Symbol()]: 222, [Symbol.iterator]() { const keys = Reflect.ownKeys(this) let index = 0 return { next() { if (index < keys.length) { return { done: false, value: keys[index++] } } return { done: true } } } } } for (let key of obj) { console.log(key) }
|
可迭代对象的使用场景:
-
JavaScript中语法:for …of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment)。
-
创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable])。
-
一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable)。
补充:
迭代器中其实还可以做一个操作,就是监听容器对象的中断操作:
如果我们想实现这个操作,就需要在迭代器对象中加入return方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const obj = { name: '111', age: 12, [Symbol()]: 222, [Symbol.iterator]() { const keys = Reflect.ownKeys(this) let index = 0 return { next() { if (index < keys.length) { return { done: false, value: keys[index++] } } return { done: true } }, return() { console.log(111) return { done: true } } } } } for (let key of obj) { console.log(key) if (key === 'age') { break } }
|
Generator(生成器)
查看
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
平时我们写的函数终止条件要么是return,要么是发生了异常,生成器中又提供了yield关键字来控制函数的执行。
生成器函数也是一个函数,但与普通的函数有一些区别:
生成器实例代码:
1 2 3 4 5 6 7 8 9 10 11 12
| function* foo(y1) { console.log(1, y1) const y2 = yield 1 console.log(2, y2) const y3 = yield 2 console.log(3, y3) }
const generator = foo('111') console.log(generator.next()) console.log(generator.next('222')) console.log(generator.next('333'))
|
生成器函数中的代码并不会默认执行,但可以通过其返回的生成器调用next的方法再根据yield关键字控制代码的执行,一次next的调用运行到一个yield关键字的位置,并返回一个对象,对象为{value: yield关键字后面的值,done: false},当next的调用返回的done为true时,表示函数执行完毕,此时返回的对象为{value: undefined, done: true}。也可以通过next方法向构造器函数中传值,通过yield关键字获取到传进来的值,但第一次next的传值调用是无法被接收到的。
对于生成器,我们可以直接调用其return和throw方法来控制函数的提前结束和抛出异常。
提前结束1 2 3 4 5 6 7 8 9 10 11 12
| function* foo(y1) { console.log(1, y1) const y2 = yield 1 console.log(2, y2) const y3 = yield 2 console.log(3, y3) }
const generator = foo('111') console.log(generator.next()) console.log(generator.return('222')) console.log(generator.next('333'))
|
以上代码可以看出,生成器函数的调用会在生成器的return方法调用时提前结束,即使后面还有代码没执行完。return方法还会返回一个对象,对象为{value: return方法传进来的值,done: true},后面即使在调用next方法,结果也都是{value: undefined,done: true}了。
抛出异常1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function* foo(y1) { console.log(1, y1) try { const y2 = yield 1 console.log(2, y2) const y3 = yield 2
console.log(3, y3) } catch (error) { console.log(error) } }
const generator = foo('111') console.log(generator.next()) console.log(generator.throw('执行异常')) console.log(generator.next('333'))
|
以上代码可以看出,生成器函数的调用会在生成器的throw方法调用时提前结束,并抛出异常,即使后面还有代码没执行完。throw方法还会返回一个对象,对象为{value: undefined,done: true},后面即使在调用next方法,结果也都是{value: undefined,done: true}了。
既然生成器是一种特殊的迭代器,那么我们可以用生成器替代迭代器,那上文的代码为例,用生成器重写后为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function* createObjIterator(obj) { let keys = Reflect.ownKeys(obj)
for (let key of keys) { yield key } }
const obj = { name: '111', age: 12, [Symbol()]: 222 }
const objIterator = createObjIterator(obj) console.log(objIterator.next()) console.log(objIterator.next()) console.log(objIterator.next()) console.log(objIterator.next())
|
用生成器实现可迭代对象的@@iterator方法,只需要在[Symbol.iterator](){}开头加一个*,那么它就是一个生成器方法了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const obj = { name: '111', age: 12, [Symbol()]: 222, *[Symbol.iterator]() { const keys = Reflect.ownKeys(this) for (let key of keys) { yield key } } } for (let key of obj) { console.log(key) }
|
上述我们实现迭代器或生成器内部获取元素的时候都是用的for of 循环,for of可以用于可迭代对象,如果内部循环的是一个可迭代对象的话,我们可以将操作简写以下。
1 2 3 4 5 6 7 8 9 10 11 12
| const obj = { name: '111', age: 12, [Symbol()]: 222, *[Symbol.iterator]() { const keys = Reflect.ownKeys(this) yield* keys } } for (let key of obj) { console.log(key) }
|
Generator解决异步回调地狱问题
查看
场景:当我们发送一个异步请求,需要根据该请求结果发送另一个请求,另一个请求的结果时其他请求所必需的,依次类推,给出回调地狱的代码,尝试对其优化。
1 2 3 4 5 6 7 8 9
| function getData(){ requestData('111').then(res1 => { requestData(res1).then(res2 => { requestData(res2).then(res3 => { console.log(res3) }) }) }) }
|
我们可以通过返回Promise对其进行优化
1 2 3 4 5 6 7 8 9
| function getData(){ requestData('111').then(res1 => { return requestData(res1) }).then(res2 => { return requestData(res2) }).then(res3 => { console.log(res3) }) }
|
以上代码看起来好多了,但阅读性其实还是比较差的,接下来我们用生成器进行优化。
1 2 3 4 5 6
| function* getData(){ const res1 = yield requestData('111') const res2 = yield requestData(res1) const res3 = yield requestData(res2) console.log(res3) }
|
上述封装请求的生成器函数看起来挺清爽的,但是调用起来就麻烦了。
1 2 3 4 5 6 7 8 9
| const generator = getData()
generator.next().value.then(res1 => { generator.next(res1).value.then(res2 => { generator.next(res2).value.then(res3 => { console.log(res3) }) }) })
|
但是这样又变成了上述用Promise解决时的问题,但好在生成器的每次next调用都会返回一个对象,对象中有一个属性done可以判断生成器函数中的代码是否执行完毕,所以我们可以对其进行封装优化。
封装自动执行generator函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function* getData() { const res1 = yield requestData('111') const res2 = yield requestData(res1) const res3 = yield requestData(res2) console.log(res3) }
function execGenerator(genFn) { const generator = genFn()
function exec(res) { const result = generator.next(res) if (result.done) return result.value result.value.then(res => { exec(res) }) } exec() }
|