有关生成器与迭代器

廖雪峰斐波那契数列案例

https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    // 廖雪峰
    // https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112
    // 普通
    function fib(max) {
      
      
      var
        t,
        a = 0,
        b = 1,
        arr = [0, 1];
      while (arr.length < max) {
      
      
        [a, b] = [b, a + b];
        arr.push(b);
      }
      return arr;
    }
    // console.log(fib(5));

    // 函数只能返回一次,所以必须返回一个Array。但是,如果换成generator,就可以一次返回一个数,不断返回多次
    function* fib(max) {
      
      
      var
        t,
        a = 0,
        b = 1,
        n = 0;
      while (n < max) {
      
      
        yield a;
        [a, b] = [b, a + b];
        n++;
      }
      return;
    }
    console.log(fib(5));//fib(5)仅仅是创建了一个generator对象,还没有去执行它。

    var f = fib(6);
    // console.log(f.next()); // 可以根据done来判断是否循环完成
    f.next(); // {value: 0, done: false}
    f.next(); // {value: 1, done: false}
    f.next(); // {value: 1, done: false}
    f.next(); // {value: 2, done: false}
    f.next(); // {value: 3, done: false}
    f.next(); // {value: undefined, done: true}

    // 第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done:
    for (var x of fib(10)) {
      
      
      // console.log(x); // 依次输出0, 1, 1, 2, 3, ...
    }

    // 那么generator和普通函数相比,有什么用?
    // 因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数
    // 用这一点,写一个generator就可以实现需要用面向对象才能实现的功能
    // 例如,用一个对象来保存状态,得这么写:

    var fib2 = {
      
      
      a: 0,
      b: 1,
      n: 0,
      max: 5,
      next: function () {
      
      
        var
          r = this.a,
          t = this.a + this.b;
        this.a = this.b;
        this.b = t;
        if (this.n < this.max) {
      
      
          this.n++;
          return r;
        } else {
      
      
          return undefined;
        }
      }
    };
    // console.log(fib2.next());
    // console.log(fib2.next());

    // 简化保存数据的例子
    var test = {
      
      
      a: 0,
      next: function () {
      
      
        // debugger;
        console.log(this);
        this.a++
        return this.a
      }
    }
    // console.log(test.next());
    // console.log(test.next());

    // 练习
    // 要生成一个自增的ID,可以编写一个next_id()函数:
    var current_id = 0;

    function next_id() {
      
      
      current_id++;
      return current_id;
    }
    // 由于函数无法保存状态,故需要一个全局变量current_id来保存数字。
    // 试用generator改写:

    // c错误
    // function* next_id() {
      
      
    //   debugger;
    //   let i = 0;
    //   i++
    //   yield i;
    // }
    function* next_id() {
      
      
      var current_id = 0;
      for (; ;) {
      
      
        current_id++;
        yield current_id;
      }
    }


    // 测试
    var
      x,
      pass = true,
      g = next_id();
    debugger;
    for (x = 1; x < 100; x++) {
      
      
      let value = g.next().value
      if (value !== x) {
      
      
        console.log(x + "  " + value);
        pass = false;
        console.log('测试失败!');
        break;
      }
    }
    if (pass) {
      
      
      console.log('测试通过!');
    }

    

  </script>
</body>

</html>

你不知道的js中 第四章 P234

4.1 打破完整运行

了 JavaScript 开发者在代码中几乎普遍依赖的一个假定:一个函数
一旦开始执行,就会运行到结束,期间不会有其他代码能够打断它并插入其间。
可能看起来似乎有点奇怪,不过 ES6 引入了一个新的函数类型,它并不符合这种运行到结
束的特性。这类新的函数被称为生成器。

var x = 1; 
function *foo() {
    
     
 x++; 
 yield; // 暂停!
 console.log( "x:", x ); 
} 
function bar() {
    
     
 x++; 
}

现在,我们要如何运行前面的代码片段,使得 bar() 在 *foo() 内部的 yield 处执行呢?

// 构造一个迭代器it来控制这个生成器
var it = foo(); 
// 这里启动foo()!
it.next(); 
x; // 2 
bar(); 
x; // 3 
it.next(); // x: 3

运行结果
在这里插入图片描述

(1) it = foo() 运算并没有执行生成器 *foo(),而只是构造了一个迭代器(iterator),这个
迭代器会控制它的执行。后面会介绍迭代器。
(2) 第一个 it.next() 启动了生成器 *foo(),并运行了 *foo() 第一行的 x++。
(3) *foo() 在 yield 语句处暂停,在这一点上第一个 it.next() 调用结束。此时 *foo() 仍
在运行并且是活跃的,但处于暂停状态。
(4) 我们查看 x 的值,此时为 2。
(5) 我们调用 bar(),它通过 x++ 再次递增 x。
(6) 我们再次查看 x 的值,此时为 3。
(7) 最后的 it.next() 调用从暂停处恢复了生成器 *foo() 的执行,并运行 console.log(…)
语句,这条语句使用当前 x 的值 3。

显然,foo() 启动了,但是没有完整运行,它在 yield 处暂停了。后面恢复了 foo() 并让它
运行到结束,但这不是必需的。

我的理解:所以就是第一次.next的时候暂停了, 输出的value是yield的值 然后done为false ; 第二次.next的时候,继续执行yield下面的代码,done为true ,value为undefined(因为函数没有返回值)
这也是为什么上面斐波那契数列每次最后一个输出都是done了 因为他需要继续执行yield后面的代码 但是并没有返回值给他
调试了一下斐波那契的代码
第一个.next执行到这里
在这里插入图片描述
第二个是从这儿开始的
在这里插入图片描述
执行完n++ 符合条件继续执行循环,遇到yield又停下来 之后返回a的值
就相当于碰到yield就卡在那儿了,下面的代码都执行不了,直到遇到下一个next
最后一个循环没有遇到yield done就为true了

因此,生成器就是一类特殊的函数,可以一次或多次启动和停止,并不一定非得要完成。
尽管现在还不是特别清楚它的强大之处,但随着对本章后续内容的深入学习,我们会看到
它将成为用于构建以生成器作为异步流程控制的代码模式的基础构件之一。

4.1.1 输入和输出

生成器函数是一个特殊的函数,这意味着它仍然有一些基本的特性没有改变。比如,它仍然可以接受参数(即输
入),也能够返回值(即输出)。


// 传参和返回
    function* foo1(x, y) {
    
    
      return x * y;
    }
    var it = foo1(6, 7);
    var res = it.next();
    res.value; // 42

foo(6,7) 看起来很熟悉。
但难以理解的是,生成器 *foo(…) 并没有像普通函数一样实际运行。
事实上,我们只是创建了一个迭代器对象,把它赋给了一个变量 it,用于控制生成器
*foo(…)。然后调用 it.next(),指示生成器 *foo(…) 从当前位置开始继续运行,停在下
一个 yield 处或者直到生成器结束。

生成器提供了更强大更引人注目的内建消息输入输出能力(这是啥),通过 yield 和 next(…) 实现。

function *foo(x) {
    
     
 var y = x * (yield); 
 return y; 
} 
var it = foo( 6 ); 
// 启动foo(..) 
it.next(); 
var res = it.next( 7 ); 
res.value; // 42

首先,传入 6 作为参数 x。然后调用 it.next(),这会启动 *foo(…)。 在 *foo(…) 内部,开始执行语句 var y = x …,但随后就遇到了一个 yield 表达式。它
就会在这一点上暂停 *foo(…)(在赋值语句中间!),并在本质上要求调用代码为 yield
表达式提供一个结果值。接下来,调用 it.next( 7 ),这一句把值 7 传回作为被暂停的
yield 表达式的结果。

我的理解: 所以说yield是可以被赋值的,以前都是通过yield返回值 下一次调用.next(xxx) 那么xxx就是给yield赋的值
这个例子就是碰到yield 暂停了,没有执行y,然后根据.next(xx) 把xx传给yield, 然后传给y return y 因为没有碰到yield,所以done为true

一般来说,需要的 next(…) 调用要比 yield 语句多一个,前面的代码片段有一个 yield 和两个 next(…) 调用。
为什么会有这个不匹配?因为第一个 next(…) 总是启动一个生成器,并运行到第一个 yield 处。不过,是第二个
next(…) 调用完成第一个被暂停的 yield 表达式,第三个 next(…) 调用完成第二个 yield,
以此类推

只考虑生成器代码:
var y = x * (yield);
return y;
第一个 yield 基本上是提出了一个问题:“这里我应该插入什么值?”
谁来回答这个问题呢?第一个 next() 已经运行,使得生成器启动并运行到此处,所以显
然它无法回答这个问题。因此必须由第二个 next(…) 调用回答第一个 yield 提出的这个
问题

看到不匹配了吗——第二个对第一个?
把视角转化一下:不从生成器的视角看这个问题,而是从迭代器的角度。

还需要解释一下:消息是双向传递的——yield… 作为一个
表达式可以发出消息响应 next(…) 调用,next(…) 也可以向暂停的 yield 表达式发送值。

我的理解:就是说第一个next可以收到第一个的yield发出的值,第二个next可以向第一个yield传递值 以此类推 消息是双向传递

function *foo(x) {
    
     
 var y = x * (yield "Hello"); // <-- yield一个值!
 return y; 
} 
var it = foo( 6 ); 
var res = it.next(); // 第一个next(),并不传入任何东西
res.value; // "Hello" 
res = it.next( 7 ); // 向等待的yield传入7
res.value; // 42

那么只看下面这一段迭代器代码:

var res = it.next(); // 第一个next(),并不传入任何东西
res.value; // "Hello" 
res = it.next( 7 ); // 向等待的yield传入7 
res.value; // 42

我们并没有向第一个 next() 调用发送值,这是有意为之。只有暂停的 yield
才能接受这样一个通过 next(…) 传递的值,而在生成器的起始处我们调用
第一个 next() 时,还没有暂停的 yield 来接受这样一个值。规范和所有兼
容浏览器都会默默丢弃传递给第一个 next() 的任何东西。传值过去仍然不
是一个好思路,因为你创建了沉默的无效代码,这会让人迷惑。因此,启动
生成器时一定要用不带参数的 next()。

第一个 next() 调用(没有参数的)基本上就是在提出一个问题:“生成器 *foo(…) 要给我
的下一个值是什么”。谁来回答这个问题呢?第一个 yield “hello” 表达式。
看见了吗?这里没有不匹配。
根据你认为提出问题的是谁,yield 和 next(…) 调用之间要么有不匹配,要么没有。
但是,稍等!与 yield 语句的数量相比,还是多出了一个额外的 next()。所以,最后一个
it.next(7) 调用再次提出了这样的问题:生成器将要产生的下一个值是什么。但是,再没
有 yield 语句来回答这个问题了,是不是?那么谁来回答呢?
return 语句回答这个问题!
如果你的生成器中没有 return 的话——在生成器中和在普通函数中一样,return 当然不
是必需的——总有一个假定的 / 隐式的 return;(也就是 return undefined;),它会在默认
情况下回答最后的 it.next(7) 调用提出的问题

4.1.2多个迭代器

有一个细微之处很容易忽略:每次构建一个迭代器,实际上就隐式构建了生
成器的一个实例,通过这个迭代器来控制的是这个生成器实例。
同一个生成器的多个实例可以同时运行,它们甚至可以彼此交互:

回想一下 1.3 节中关于完整运行的这个场景:

var a = 1; 
var b = 2; 
function foo() {
    
     
 a++; 
 b = b * a; 
 a = b + 3; 
} 
function bar() {
    
     
 b--; 
 a = 8 + b; 
 b = a * 2; 
}

如果是普通的 JavaScript 函数的话,显然,要么是 foo() 首先运行完毕,要么是 bar() 首
先运行完毕,但 foo() 和 bar() 的语句不能交替执行。所以,前面的程序只有两种可能的
输出

但是,使用生成器的话,交替执行(甚至在语句当中!)显然是可能的

未完待续

收藏链接

链接

猜你喜欢

转载自blog.csdn.net/xiaozhazhazhazha/article/details/121621896