迭代器、生成器和async

210 阅读4分钟
迭代器iterator
基本概念:

迭代器是帮助我们对某个数据结构进行遍历的对象,迭代器是一个对象,这个对象必须符合迭代器协议。

迭代器协议中定义了产生一系列值的标准方法,在js中这个标准就是next

const names = [123,321,231]
let index = 0;
const nameIterator = {
  next(){
    if(index<names.length){
      return {done:false,value:names[index++]}
    }
    else{
      return {done:true,value:undefined}
    }
  }
}
console.log(nameIterator.next());
console.log(nameIterator.next());
console.log(nameIterator.next());
console.log(nameIterator.next());
//输出
//{ done: false, value: 123 }
//{ done: false, value: 321 }
//{ done: false, value: 231 }
//{ done: true, value: undefined }

观察上述代码,迭代器内部的迭代的数据是固定的也就是写死的(names数组),如果想要进行迭代器复用的话,可以用下面这种方式(函数返回对象)来解决。

const arr1  = [1,2,3,4,5]
const arr2 = ['a','b','c','d','e']
function createInterator(data){
  let index = 0;
  return {
    next(){
      if(index<data.length){
        return {done:false,value:data[index++]}
      }
      else{
        return {done:true,value:undefined}
      }
    }
  }
}
const arr1Interator = createInterator(arr1);
console.log(arr1Interator.next());
const arr2Interator = createInterator(arr2);
console.log(arr2Interator.next());

迭代器是一个对象

符合迭代器协议(iterator protocol) : next函数

const iterator = { next : function () { return { } } }


可迭代对象 是一个对象

符合可迭代协议 (iterable protocol) : [Symbol iterator] 函数

const iterableObj = { [Symbol iterator] : function () { return { 迭代器 } } }


可迭代对象的写法,for...of...遍历的就是可迭代对象

const iterator = {
  names:['abc','cba','nba'],
  [Symbol.iterator]: function(){
    let index = 0
    return {
      next:()=>{
        if(index<this.names.length){
          return {done:false,value:this.names[index++]}
        }
        else{
          return {done:true,value:undefined}
        }
      }
    }
  }
}
for(let item of iterator){
  console.log(item);//abc cba nba
}

数组天生就是一个可迭代对象

const arr = [1,2,3,4,5]
console.log(arr[Symbol.iterator]().next());//{ value: 1, done: false }

String,Array,Map,Set,arguments,NodeList都是可迭代对象,但是普通对象是不可迭代的。

可迭代对象的应用场景
  1. 展开语法

    只有可迭代对象可以被展开,这里就不得不提到普通对象也可以用展开运算符进行操作,普通对象进行展开运算符操作的时候,用的不是展开运算符的语法,而是其他的语法。也就是说数组等可迭代对象的展开运算符和普通对象的展开运算符内部实现完全不同,但是都叫作展开运算符。

  2. for...of.... 遍历可迭代对象

  3. 解构语法

    对数组可进行解构赋值,同时普通对象的解构赋值,不是利用可迭代的语法。

  4. 创建一些对象

    创建set时,要传入可迭代对象

    Array.from()传值也需要可迭代对象

    Promise.all()传值需要传入一个可迭代对象,传入数组也可以,数组就是可迭代对象。

自定义可迭代性
class Classroom{
  constructor(name,address,list){
    this.name = name
    this.address = address
    this.list = list
  }
  entry(member){
    this.list.push(member)
  }
  [Symbol.iterator](){
    let index = 0;
    return {
      next:()=>{
        if(index<this.list.length){
          return {done:false,value:this.list[index++]}
        }
        else{
          return {done:true,values:undefined}
        }
      }
    }
  }
}
let c = new Classroom('123','123',[1,2,3,4])
c.entry(5)
console.log(c);
for(let item of c){
  console.log(item);
}

如果需要用构造函数来自定义的话,需要把方法添加到原型对象上

生成器
基本概念:

生成器是一种特殊的迭代器,生成器generator函数是es6提出的一种异步编程的解决方案。它和普通的函数不太一样。可以把generator函数看成一个状态机,在内部它封装了很多的状态,可暂停可返回(yield和next)

  1. 生成器函数也是一个函数,定义函数前需要加* 如:function* foo(){}

    这个*的位置,只要在function关键字和函数名之间就可以

  2. 通过yield来控制函数的暂停和执行

  3. 生成器函数的返回值是一个生成器。

function* fn(){
  console.log(1);
  yield
  console.log(2);
  yield
  console.log(3);
  yield
  console.log(4);
}
let generator = fn()
generator.next()
generator.next()
generator.next()
//1 2 3 

在调用generator函数后,函数内部的代码不会执行,返回的也不是函数执行的结果。而是返回一个生成器函数。

yield表达式起到一个暂停标记的作用,

next函数起到一个恢复执行的作用,同样的每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。next传值{value,done}对象中现接受yield右边的值,后续代码才是用的是传入的值(如下代码)

  • value:暂停点后面接的值,也就是yield后面接的值
  • done:是否generator函数已走完,没走完为false,走完为true
function* gen(){
  const num1 = yield 1
  console.log(num1);
  const num2 = yield 2
  console.log(num2);
  return 3
}
const g = gen()
console.log(g.next())
console.log(g.next(2222))
//{ value: 1, done: false }
//2222
//{ value: 2, done: false }  

调用next方法会使指针指向下一个状态(我们帮generator函数看成一个状态机,封装了很多状态,而yield就是状态的边界),换句话来说,调用next后,generator函数中的代码从函数头或者上一下暂停的位置,开始运行,直到遇到下一个yield或者函数执行完毕。

注:代码执行到yield后 会将yield后的函数执行完,然后将函数的返回值作为value,然后暂停运行

generator其实就是JS在语法层面对协程的支持,真正支持与否看运行时环境,比如高版本的node就是支持的。协程就是主程序和子协程直接控制权的切换,并伴随通信的过程,那么,从generator语法的角度来讲,yield,next就是通信接口,next是主协程向子协程通信,而yield就是子协程向主协程通信

与 Iterator 接口的关系

因为可迭代对象的Symbol.iterator方法是一个生成迭代器的方法。generator函数本身也是生成迭代器的函数,所以可以把generator函数赋值给Symbol.iterator 示例代码:

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};[...myIterable] // [1, 2, 3]
next 方法的参数

next方法可以传入一个参数,这个参数会被当做上一个yield的返回值,第一个next传值是没有用的

function* fn(){
  console.log(1);
  const n = yield
  console.log(n);
}
let generator = fn()
generator.next()
generator.next(100)//如果不传值 输出 1 undefined 传了100进去后 输出 1 100

yield* 后边返回一个可迭代对象

function* fn(){
  yield* ['abc','cba','nba']
}
let generatorFn = fn()
console.log(generatorFn);
console.log(generatorFn.next());
console.log(generatorFn.next());
console.log(generatorFn.next());
//Object [Generator] {}
//{ value: 'abc', done: false }
//{ value: 'cba', done: false }
//{ value: 'nba', done: false }

async函数

async函数是generator和promise的语法糖

  1. await只能在async函数里使用,不然会报错
  2. async函数返回的是一个promise,有没有值看return值
  3. async配合await的作用是用同步方法,执行异步操作
利用promise和generator手动实现async/await

众所周知,yield后边接的函数会被执行,然后将函数的返回值作为value值。因为要进行异步操作,所以想到promise来写,如下代码

function fn(num){
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      resolve(num)
    }, 2000);
  })
}
function* gen(){
  yield fn(1)
  yield fn(2)
  console.log(1);
}
let gen1 = gen()
console.log(gen1.next());
console.log(gen1.next());
//{ value: Promise { <pending> }, done: false }
//{ value: Promise { <pending> }, done: false }
// 1

因为yield后边的函数时立刻执行的,然而promise还没有确定状态,所以会返回pending状态的promise。我们的终极目的是让异步函数同步执行并且异步函数都能执行完全(两个promise分别获得1,2)。

所以我们需要用promise的then方法来继续执行

const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
  console.log(next1) // 1秒后输出 { value: Promise { 1 }, done: false }
  console.log(res1) // 1秒后输出 1

  const next2 = g.next()
  next2.value.then(res2 => {
    console.log(next2) // 2秒后输出 { value: Promise { 2 }, done: false }
    console.log(res2) // 2秒后输出 2
    console.log(g.next()) // 2秒后输出 { value: 3, done: true }
  })
})

注意:这个是嵌套结构的!!!

待续................................