前端面试复习2:迭代器,生成器与异步迭代器

最近在刷面试题,这次学习下 迭代器,生成器与异步迭代器(头大)。

话不多说开始撸:

原文地址:我的博客

Iterator 迭代器

iterator 是一个特殊的对象,它有用一个 next 方法,next 方法返回一个对象。

这个对象包含两个属性:

  1. value: any,表示成员的值
  2. done: boolean,表示迭代器是否结束

iterator.next() // 返回 {value: '', done: false}

迭代器内部会保存一个指针,指向迭代器的成员位置,每调用一次 next 方法,指针就会移动到下一个成员

直到指针指向迭代器最后一个成员后面的位置

这时,done 的值为 truevalue 的值一般为 undefined,需要根据 iterator 的实际实现来决定。

简单实现一个迭代器类

成员属性介绍:

  • private point: number = 0 指针 初始为0
  • private params: any[] | Object 传入可迭代数据
  • private keys: any[] 可迭代数据的 key
  • private 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...infor...of 区别

  1. 推荐在循环对象属性的时候,使用 for...in会遍历到原型链),在遍历数组的时候的时候使用 for...ofObject.getOwnPropertyNames() 只会拿到自身可枚举非可枚举的属性)
  2. for...in 循环出的是 keyfor...of 循环出的是 value
  3. for...of 是ES6新引入的特性
  4. for...of 不能循环没有 [Symbol.iterator] 属性的对象,需要通过和 Object.keys() 搭配使用

拥有了 iterator 接口的数据结构,也就是具有 Symbol.iterator 方法的数据结构,就可以被 for...of 遍历。

Symbol.iterator 方法类似于上面实现的 MyIterator

  1. 数组天生部署了迭代器接口
const array = [1, 2, 3];
typeof array[Symbol.iterator] // 'function'

for (const val of array) {
    console.log(val);
}
// 1
// 2
// 3
复制代码
  1. 对象没有迭代器接口
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'
复制代码
  1. 当迭代器遍的状态已经完成时候,不会再更改状态

Generator 生成器

Generator 是一个特殊的函数,函数体内部使用 yield 表达式,定义不同的内部状态。

当执行 Generator 函数时,不会直接执行函数体,而是会返回一个 迭代器对象(iterator)

  1. Generator 函数内部可以使用 yield 表达式,定义内部状态
  2. 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 到底在等啥?

MDN文档

还是这个例子:

async function fetchInfo () {
  const name = await ajaxName();
  console.log(name); // 测试
  const sex = await ajaxSex();
  console.log(sex); // 男
  const school = await ajaxSchool();
  console.log(school); // 辽宁大学
}
复制代码
  1. await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。
  2. Promise 正常处理(fulfilled),其**构造函数第一个参数 resolve 函数的参数(resolve(value))**作为 await 表达式的值,继续执行 async function
  3. Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
  4. 另外,如果 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[]
复制代码

总结

  1. Iterator 是一个可迭代接口,任何实现了此接口的数据结构都可以被 for...of 循环遍历
  2. Generator 是一个可以暂停和继续执行的函数体,可以完全实现 Iterator 的功能,并且由于可以保存上下文,非常适合实现简单的状态机。另外通过一些流程控制代码的配合,可以比较容易进行异步操作。
  3. Async/Await 就是 Generator 进行异步操作并封装了 Promise的语法糖。返回值为 Promise 的函数。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值