【问题标题】:Function generators in for loopsfor 循环中的函数生成器
【发布时间】:2018-12-28 22:03:21
【问题描述】:

我正在尝试更好地了解生成器在 javascript 中的工作方式。

来自 MDN:

function* 声明(function 关键字后跟一个星号) 定义了一个生成器函数,它返回一个 Generator 对象。

function *range(from, to) {
    var counter = from;
    while(to >= counter) {
        yield counter
        counter++
    }        
}

for (var r of range(5, 10)) {
    console.log( r );
}

// print: 5, 6, 7, 8, 9, 10

我不确定我是如何理解上面的 sn-p 中发生了什么。

生成器不应该被调用,存储为(生成器)对象,然后由next() 方法调用。 (如下所示)

function *foo () {
      yield 'woo';
 }
 var G = foo();
 console.log( G.next() );

在上面的代码中,在第 4 行,var G = foo();不是调用函数并创建新的执行上下文,这应该只返回一个生成器对象(并将其存储在标签 @987654325 下@)。

当我在第 5 行调用 next() 方法时,我将调用实际函数 foo。此时,我正在创建一个执行上下文,执行 foo 中的代码并产生取出字符串"woo"

第一个 sn-p 究竟应该如何工作?

【问题讨论】:

    标签: javascript for-loop generator


    【解决方案1】:

    调用生成器函数将返回一个迭代器(具有.next 函数的对象),而for..of 循环将自动 迭代可迭代对象。虽然您可以事先将迭代器存储在变量中:

    const iter = range(5, 10);
    for (var r of iter) {
      ...
    }
    

    没必要——毕竟for..of 只需要一个迭代器的引用。

    您可以在代码中模仿这一点,将一个迭代器的引用传递给一个函数,该函数调用每个 .next 函数,直到迭代器耗尽:

    function *range(from, to) {
        var counter = from;
        while(to >= counter) {
            yield counter
            counter++
        }        
    }
    
    iterate(range(5, 10), num => {
      console.log(num);
    });
    
    function iterate(iterator, callback) {
      while (true) {
        const { value, done } = iterator.next();
        if (done) return;
        callback(value);
      }
    }

    如您所见,在将迭代器传递给 iterate 之前,无需将迭代器存储在变量中,就像您可以使用 range(5, 10) 直接调用 for..of 循环一样,因为循环(或函数) 的内部为您完成所有迭代。

    【讨论】:

    • 不是 range(5, 10) 返回对象的唯一目标吗?这里看起来它实际上运行了 *range() 函数。
    • 看起来循环内部隐含地使调用成为 range 的方法,而我本来期望更像 range(5, 10).next()。
    • 是的,它确实运行 *range 生成器函数,该函数返回一个迭代器,然后该迭代器可以传递给 for 循环(或传递给像 iterate 这样的其他函数)将遍历迭代器。
    • while (true) { const { value, done } = iterator.next(); if (done) return; callback(value); } 是有道理的。所以,这个 while 循环的逻辑,以及对iterator.next() 的更明确的调用,都被for .. of 循环伪装了。对吗?
    • 对,for..of 循环自动通过迭代器,类似于iterate 函数如何通过迭代器。手动计数for (let i = .. 循环在这种情况下不起作用(至少,不是很好),因为没有办法预先知道迭代器在完成之前将经历多少次迭代。也可以像在iterate 函数中一样使用while 循环。
    【解决方案2】:

    所以,对您的问题的简短回答是 for ... of loop 实际上只需要一个符合 Iterable Protocol 的对象。这是一个对象,它的函数绑定到符号键Symbol.iterator,它返回一个可迭代对象。

    生成器实际上有这个属性next 函数(这是iterator protocol 的实现所必需的)。你可以在下面的 sn-p 中看到这一点。

    const f = function*() {
      let i = -1; 
      while(true){
            i = i + 1; 
            yield i;
      }
    };
    
    const generator = f();
    console.log(generator[Symbol.iterator]);
    console.log(generator.next);

    所以,这就是为什么您不需要为for...of 循环创建生成器对象的实例,也不需要显式调用next。由于 Iterable 协议的约定,这可以自动处理。

    话虽如此,您可以通过从生成器对象创建迭代器对象(或直接调用next)来模仿for...of 循环和while 循环的行为,如下所示:

    const f = function*() {
      let i = -1; 
      while(true){
            i = i + 1; 
            yield i;
      }
    };
    
    const generator = f();
    const iterator = generator[Symbol.iterator]();
        
    let j = 0;
    let next;
    while(j < 5) {
          next = iterator.next();
          console.log('next: ', next);
          j = next.value;
    }

    原则上,这可能也是 for...of 循环的本机代码所做的。

    【讨论】:

      猜你喜欢
      • 2015-04-01
      • 2015-06-16
      • 1970-01-01
      • 2016-05-01
      • 2017-10-05
      • 2020-05-19
      • 2014-07-20
      • 2019-01-04
      相关资源
      最近更新 更多