最近在刷面试题,这次学习下 迭代器,生成器与异步迭代器(头大)。
话不多说开始撸:
原文地址:我的博客
Iterator 迭代器
iterator
是一个特殊的对象,它有用一个 next
方法,next
方法返回一个对象。
这个对象包含两个属性:
value: any
,表示成员的值done: boolean
,表示迭代器是否结束
iterator.next() // 返回 {value: '', done: false}
迭代器内部会保存一个指针,指向迭代器的成员位置,每调用一次 next
方法,指针就会移动到下一个成员
直到指针指向迭代器最后一个成员后面的位置
这时,done
的值为 true
,value
的值一般为 undefined
,需要根据 iterator
的实际实现来决定。
简单实现一个迭代器类
成员属性介绍:
private point: number = 0
指针 初始为0private params: any[] | Object
传入可迭代数据private keys: any[]
可迭代数据的 keyprivate length: number
可迭代数据的 key 的长度
class MyIterator {
private point: number = 0;
private params: any[] | Object;
private keys: any[];
private length: number;
constructor(params: any[] | Object) {
this.params = params;
this.keys = Object.keys(params);
this.length = this.keys.length;
}
public next(): { done: boolean; value: any; } {
const done = this.point >= this.length;
const value = done? undefined : this.params[this.keys[this.point]];
if (!done) this.point++;
return {
done,
value,
}
}
}
const iterator = new MyIterator([1,2,3]);
console.log(1, iterator.next()); // 1 { done: false, value: 1 }
console.log(2, iterator.next()); // 2 { done: false, value: 2 }
console.log(3, iterator.next()); // 3 { done: false, value: 3 }
console.log(4, iterator.next()); // 4 { done: true, value: undefined }
console.log(5, iterator.next()); // 5 { done: true, value: undefined }
复制代码
iterator接口
首先介绍下 for...in
与 for...of
区别
- 推荐在循环对象属性的时候,使用
for...in
(会遍历到原型链),在遍历数组的时候的时候使用for...of
(Object.getOwnPropertyNames()
只会拿到自身可枚举非可枚举的属性) for...in
循环出的是key
,for...of
循环出的是value
for...of
是ES6新引入的特性for...of
不能循环没有[Symbol.iterator]
属性的对象,需要通过和Object.keys()
搭配使用
拥有了 iterator
接口的数据结构,也就是具有 Symbol.iterator
方法的数据结构,就可以被 for...of
遍历。
Symbol.iterator
方法类似于上面实现的 MyIterator
。
- 数组天生部署了迭代器接口
const array = [1, 2, 3];
typeof array[Symbol.iterator] // 'function'
for (const val of array) {
console.log(val);
}
// 1
// 2
// 3
复制代码
- 对象没有迭代器接口
const obj = {a: 'a1', b: 'b1', c: 'c1'};
typeof obj[Symbol.iterator] // 'undefined'
for (const val of obj) {
console.log(val);
}
// VM974:4 Uncaught TypeError: obj is not iterable
obj[Symbol.iterator] = function() {
const keys = Object.keys(this);
const len = keys.length;
let pointer = 0;
return {
next() {
const done = pointer >= len;
const value = !done ? self[this[pointer++]] : undefined;
return {
value,
done
};
}
}
}
for (const val of obj) {
console.log(val);
}
// 'a'
// 'b'
// 'c'
复制代码
- 当迭代器遍的状态已经完成时候,不会再更改状态
Generator 生成器
Generator
是一个特殊的函数,函数体内部使用 yield
表达式,定义不同的内部状态。
当执行 Generator
函数时,不会直接执行函数体,而是会返回一个 迭代器对象(iterator)。
Generator
函数内部可以使用yield
表达式,定义内部状态function
关键字与函数名之间有一个*
function* generator() {
yield 1;
yield 2;
return 3;
}
const myIterator = generator();
// 当调用iterator的next方法时,函数体开始执行,
console.log(myIterator.next()); // {value: 1, done: false}
console.log(myIterator.next()); // {value: 2, done: false}
console.log(myIterator.next()); // {value: 3, done: true}
for (const val of myIterator) {
console.log(val); // 不会触发
}
复制代码
当 for...of
之后,迭代器的状态会关闭。
实现个中序遍历(网上看到的,很屌)
function* traverseTree(node) {
if (node == null) return;
yield* traverseTree(node.left);
yield node.value;
yield* traverseTree(node.right);
}
复制代码
gennerator嵌套
生成器函数中使用生成器函数 需要使用 *
当我们想在 generator b
中嵌套 generator a
时,怎么嵌套呢?
yield *a(); ==> yield 1
,实际上就是把 yield 1
放在这个位置
所以在生成器函数中使用生成器函数 需要使用 *
function* a(){
yield 1;
}
function* b(){
yield* a();
yield 2;
}
let it = b();
console.log(it.next()); // { value: 1, done: false }
复制代码
实现异步迭代
// ajax 是一个返回 Promise 的函数
function ajaxName() {
return Promise.resolve('测试');
}
function ajaxSex() {
return Promise.resolve('男');
}
function ajaxSchool() {
return Promise.resolve('辽宁大学');
}
function * fetchInfo () {
const name = yield ajaxName();
const sex = yield ajaxSex();
const school = yield ajaxSchool();
}
const info = fetchInfo();
// 加入的控制代码
info.next().value.then(name => {
console.log(name); // 测试
return info.next().value;
}).then(sex => {
console.log(sex); // 男
return info.next().value;
}).then(school => {
console.log(school); // 辽宁大学
});
复制代码
async/await 异步迭代器
async/await
其实相当于简化了Generator
更改下上面的异步迭代器:
// 相同
async function fetchInfo () {
const name = await ajaxName();
console.log(name); // 测试
const sex = await ajaxSex();
console.log(sex); // 男
const school = await ajaxSchool();
console.log(school); // 辽宁大学
}
复制代码
-
await
只能用在async
关键词的函数中 -
async
函数返回一个Promise
-
async/await
相当于封装了Promise
await 到底在等啥?
还是这个例子:
async function fetchInfo () {
const name = await ajaxName();
console.log(name); // 测试
const sex = await ajaxSex();
console.log(sex); // 男
const school = await ajaxSchool();
console.log(school); // 辽宁大学
}
复制代码
await
表达式会暂停当前async function
的执行,等待Promise
处理完成。- 若
Promise
正常处理(fulfilled),其**构造函数第一个参数resolve
函数的参数(resolve(value)
)**作为await
表达式的值,继续执行async function
。 - 若
Promise
处理异常(rejected),await
表达式会把Promise
的异常原因抛出。 - 另外,如果
await
操作符后的表达式的值不是一个Promise
,则返回该值本身。a = await 10
a就是10
上面的异步函数相当于:
function fetchInfo() {
let name, sex, school;
return ajaxName().then(name => {
name = name;
console.log(name); // 测试
return ajaxSex();
}).then(sex => {
sex = sex;
console.log(sex); // 男
return ajaxSchool();
}).then(school => {
school = school;
console.log(school); // 辽宁大学
});
}
复制代码
async/await 并发
我们的代码在执行到 await
的时候会等待结果返回才执行下一行,这样如果我们有很多需要异步执行的操作就会变成一个串行的流程,可能会导致非常慢。
比如如下代码,我们需要遍历获取 redis
中存储的100个用户的信息:
const ids = [1,2,3,4];
const users=[];
for (let i=0;i<ids.length;i++) {
users.push(await db.get(ids));
}
复制代码
由于每次数据库读取操作都要消耗时间,这个接口将会变得非常慢。
如果我们把它变成一个并行的操作,将会极大提升效率
const ids = [1,2,3,4];
const users=[];
const p = ids.map(async (id) => await db.get(id)); // any[]
const users = await Promise.all(p); // any[]
复制代码
总结
Iterator
是一个可迭代接口,任何实现了此接口的数据结构都可以被for...of
循环遍历Generator
是一个可以暂停和继续执行的函数体,可以完全实现Iterator
的功能,并且由于可以保存上下文,非常适合实现简单的状态机。另外通过一些流程控制代码的配合,可以比较容易进行异步操作。Async/Await
就是Generator
进行异步操作并封装了Promise
的语法糖。返回值为Promise
的函数。