我正在尝试使用生成器在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
尝试了cloneDeep
和lodash
,但没有用。以这种方式返回的迭代器是本机函数并在内部保持其状态,因此似乎没有办法用自己的JS代码来实现它。
答案 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
以及它们的进展程度,然后清理历史记录以消除所有克隆已经消耗的所有过去值。