如何克隆ES6生成器?

时间:2014-10-03 13:16:52

标签: javascript ecmascript-6 clone generator

我正在尝试使用生成器在ES6中创建List monad。为了使它工作,我需要创建一个已经消耗了几个状态的迭代器的副本。如何在ES6中克隆迭代器?

function* test() {
    yield 1;
    yield 2;
    yield 3;
}

var x = test();
console.log(x.next().value); // 1
var y = clone(x);
console.log(x.next().value); // 2
console.log(y.next().value); // 2 (sic)

我在clone尝试了cloneDeeplodash,但没有用。以这种方式返回的迭代器是本机函数并在内部保持其状态,因此似乎没有办法用自己的JS代码来实现它。

3 个答案:

答案 0 :(得分:6)

  

迭代器[...]在内部保持状态,所以似乎没有办法

是的,这是有充分理由的。你不能克隆状态,否则你可能会篡改生成器。

然而,有可能创建第二个迭代器,它通过记忆它的序列并在以后再次产生它来运行第一个。但是,应该只有一个迭代器真正驱动生成器 - 否则,哪些克隆将被允许发送next()个参数?

答案 1 :(得分:5)

我为JavaScript编写了一个do-notation库,burrido。为了解决可变生成器问题,我创建了immutagen,它通过维护输入值的历史记录来模拟不可变生成器,并重放它们以克隆任何特定状态的生成器。

答案 2 :(得分:3)

你不能克隆一个生成器 - 它只是一个没有状态的函数。什么可能有状态,因此可以克隆的是由调用生成器函数产生的迭代器

这种方法可以缓存中间结果,因此克隆的迭代器可以在必要时访问它们,直到它们“赶上”。它返回一个既是迭代器又是可迭代的对象,因此您可以在其上调用next或在其上调用for...of。可以传入任何迭代器,因此理论上你可以通过传入array.values()克隆数组上的迭代器。无论哪个克隆在迭代中的给定点首先调用next,都会将传递给next的参数(如果有)反映在基础生成器中的yield的值中。

function clonableIterator(it) {
  var vals = [];

  return function make(n) {
    return {
      next(arg) {
        const len = vals.length;
        if (n >= len) vals[len] = it.next(arg);
        return vals[n++];
      },
      clone()   { return make(n); },
      throw(e)  { if (it.throw) it.throw(e); },
      return(v) { if (it.return) it.return(v); },
      [Symbol.iterator]() { return this; }
    };
  }(0);
}

function *gen() {
  yield 1;
  yield 2;
  yield 3;
}

var it = clonableIterator(gen());

console.log(it.next());
var clone = it.clone();
console.log(clone.next());
console.log(it.next());

显然,这种方法存在的问题是它保留了迭代器的整个历史记录。一个优化是保留所有克隆迭代器的WeakMap以及它们的进展程度,然后清理历史记录以消除所有克隆已经消耗的所有过去值。