js 生成器和迭代器

JS生成器和迭代器

迭代器

为什么会说到这个呢?原因就是从扩展运算符和for…of的使用中发现这个问题的

let b = [1,2,3,4]
Math.max(...b)
=>4
let a ={1:2,5:6,8:9}
Math.max(...a)
=>Uncaught TypeError: a is not iterable

嗯,我们从提示上知道a并不是可迭代的,那怎么做呢?看一下数组上的原型方法

在这里插入图片描述
MDN对于迭代器的描述:

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 valuedone 一起存在,则它是迭代器的返回值。

a[Symbol.iterator]=function(){
  let keys = Object.keys(a);
  let len = keys.length;
  let values = Object.values(a);
  let n = 0;
  return {
    next: function() {
        if (n < len) {
          return {
            value: {k: keys[n], v: values[n++]},
            done: false
          }
        } else {
          return {
	          done: true // 注意,一旦 done 为 true ,此时的 value 不返回
        }
      }
    }
  }
}
// 此时打印
console.log(...a)
=>Object { k: "1", v: 2 }
Object { k: "5", v: 6 }
Object { k: "8", v: 9 }

StringArrayTypedArrayMapSet 都是内置可迭代对象,因为它们的原型对象都拥有一个 Symbol.iterator 方法。

记下内置对象谁是可迭代的,明显没有Object。

做完自定义迭代器之后就会发现,这个next()的用跟某某某很像啊,会不会有什么关联呢??

先来看看它是谁:

生成器

没错生成器,一个es6很难懂的特性(还有promise),当然其实它es5就提出来了。

再来看看MDN的解释:

虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。 生成器函数使用 function*语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。

的确,我们的迭代器它整个的next流程是可以由我们自己编写的,就像上面那个例子,next会有自己对应的一个返回值,在内部n++更改value并且到头之后done

经典斐波那契数列
function* fibonacci() {
  var fn1 = 0;
  var fn2 = 1;
  while (true) {  
    var current = fn1;
    fn1 = fn2;
    fn2 = current + fn1;
    var reset = yield current;
    if (reset) {
        fn1 = 0;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
console.log(sequence.next().value);     // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
console.log(sequence.next().value);     // 5
console.log(sequence.next().value);     // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
yield

这里需要学一下yield关键字

yield关键字实际返回一个IteratorResult对象,它有两个属性,valuedonevalue属性是对yield表达式求值的结果,而donefalse,表示生成器函数尚未完全完成

一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法被调用。每次调用生成器的next()方法时,生成器都会恢复执行,直到达到以下某个值:

  • yield,导致生成器再次暂停并返回生成器的新值。 下一次调用next()时,在yield之后紧接着的语句继续执行。
  • throw用于从生成器中抛出异常。这让生成器完全停止执行,并在调用者中继续执行,正如通常情况下抛出异常一样。
  • 到达生成器函数的结尾;在这种情况下,生成器的执行结束,并且IteratorResult给调用者返回undefined并且donetrue
  • 到达return 语句。在这种情况下,生成器的执行结束,并将IteratorResult返回给调用者,其值是由return语句指定的,并且donetrue(这种情况要注意一下,生成器结束不代表下一次next就会继续输出返回值了,因为返回值所在的执行上下文已经被销毁我们后续的next其实都找不到value了,也就是后续都会输出undefined而不是返回值)。

嗯,四种情况,第一种类似上面数列暂停开始暂停开始这样的过程。第二种是抛出错误,第三种为没有return值,即return undefined,最后是而我们正常的return 此时IteratorResult中的done为true;

那问题来了,return yield呢?

function *test(){
  yield 1;
  return yield 2;
}
var a = test()
a.next()
=>Object { value: 1, done: false }

a.next()
=>Object { value: 2, done: false }

a.next()
=>Object { value: undefined, done: true }

从结果可以看出来,先是运行yield 2 得到Object { value: 2, done: false },然后才是执行return 语句,因为yield关键字并没有返回值,此时return 出来的value就会是undefined

再看下yield委托

yield*

yield* 表达式迭代操作数,并产生它返回的每个值。

yield* 表达式本身的值是当迭代器关闭时返回的值(即donetrue时)。

  1. 产生它返回的每个值

    什么意思呢?意思就是yield* 后面可以跟另一个generator 或可迭代对象,然后通过next()来获取generator 或可迭代对象他们每次迭代返回的值。这里就可迭代对象就是我们前面提到的数组,字符串,map,set等,嗯,迭代器跟生成器就能串起来用了。

  2. 表达式返回 关闭时返回的值

    也挺好理解的,yield* 不会像yield一样没有返回值,他会返回后面的生成器或者可迭代对象done=false时的value。

    不过默认情况下对于可迭代对象关闭时他提供的value都是undefined

    function* g() {
      yield* [1, 2, 3];return 4;
    }
    
    var result;
    function* gg() {
      result = yield* g();
    }
    
    var iterator = gg();
    result
    =>undefined // 此时gg()函数暂停在yield*表达式也就是g()中的yield*[1,2,3]也就是yield 1这里,因此赋值语句仍然未执行,result的值为undefined
    iterator.next()
    =>Object { value: 1, done: false }
    
    iterator.next()
    =>Object { value: 2, done: false }
    
    iterator.next()
    =>Object { value: 3, done: false }
    
    iterator.next()
    Object { value: undefined, done: true }
    
    result
    =>4
    

    此时可以看到result已经输出4了也就是前面g函数关闭返回的value,同时由于gg函数它并没有return值,迭代器结束的时候他的value是为undefined。

讲到这基本生成器用法也算是熟悉了,再来写一个挺有意思的东西

异步生成器

也就是async await 和yield组合起来的时候会发生什么事情呢?

async function* combind(){
    await new Promise((resolve,reject)=>{resolve('a');}).then(console.log);
    yield 1;
    return 2;
}
var a = combind();
a
=>AsyncGenerator {  }

控制台打印了a的类型就叫异步生成器,看来是一个特殊的东西;正常我们的generator他next返回的是一个IteratorResult对象,也就是一个包含value跟done两个键的一个对象,那么异步生成器呢?

a.next()
=>a
=>Promise { <state>: "pending" }

a.next()
=>Promise { <state>: "pending" }

a.next()
=>Promise { <state>: "fulfilled", <value>: {<value>: {value: undefined,done: true,}}

嗯?怎么是一个Promise对象,这倒是类似await后的语句要返回一个Promise,而且这里三次next后promise的状态就转换为fullfilled了且带了个IteratorResult?试试then打印值;

a.next().then(console.log)
=>a
=>Object { value: 1, done: false }

a.next().then(console.log)
=>Object { value: 2, done: true }

a.next().then(console.log)
=>Object { value: undefined, done: true }
//输出结果基本等于
function* combind(){
    yield 1;
    return 2;
}

​ 到了这里我们豁然开朗,异步生成器的await关键字跟原来的操作还是一样,但是它得根据生成器执行到了哪里来决定本身有没有被执行。yield关键字则有较大的变化,他不再返回一个对象而是一个promise异步的迭代器,这个迭代器resolve了我们IteratorResult对象,所以我们可以通过.then来访问结果;同时在生成器执行结束之后promise的状态将会变成fullfilled。

那么,既然它返回的是迭代器,我们能不能配合for…of来使用呢?

async function* combind(){
    await new Promise((resolve,reject)=>{resolve('a');}).then(console.log);
    yield '1/3';
    await new Promise((resolve,reject)=>{resolve('b');}).then(console.log);
    yield '2/3';
    await new Promise((resolve,reject)=>{resolve('c');}).then(console.log);
    yield '3/3';
}
var a =combind();
(async ()=>{for await(const i of a)console.log(i)})()
=>a
=>1/3 
=>b
=>2/3
=>c
=>3/3

是的,这个样例也揭示了这个生成器非常适用于进度条问题。

总结

本文通过对生成器迭代器的语法进行了简要的学习,找出两者间的异同点,再扩展了一个异步生成器,对生成器有了新的认识。

参考文章:

MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators

异步生成器:https://segmentfault.com/a/1190000020499552

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值