for..of 和迭代器状态
for..of and the iterator state
考虑这个 python 代码
it = iter([1, 2, 3, 4, 5])
for x in it:
print x
if x == 3:
break
print '---'
for x in it:
print x
它打印 1 2 3 --- 4 5
,因为迭代器 it
记住它在循环中的状态。当我在 JS 中做看似相同的事情时,我得到的只是 1 2 3 ---
。
function* iter(a) {
yield* a;
}
it = iter([1, 2, 3, 4, 5])
for (let x of it) {
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it) {
console.log(x)
}
我错过了什么?
不幸的是,JS 中的生成器对象不可重用。
在 MDN
上明确说明
Generators should not be re-used, even if the for...of loop is
terminated early, for example via the break keyword. Upon exiting a
loop, the generator is closed and trying to iterate over it again does
not yield any further results.
如前所述,发电机是一次性的。
但是通过将数组包装在闭包中并返回一个新的生成器来模拟 re-usable 迭代器很容易..
例如
function resume_iter(src) {
const it = src[Symbol.iterator]();
return {
iter: function* iter() {
while(true) {
const next = it.next();
if (next.done) break;
yield next.value;
}
}
}
}
const it = resume_iter([1,2,3,4,5]);
for (let x of it.iter()) {
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it.iter()) {
console.log(x)
}
console.log("");
console.log("How about travesing the DOM");
const it2 = resume_iter(document.querySelectorAll("*"));
for (const x of it2.iter()) {
console.log(x.tagName);
//stop at first Script tag.
if (x.tagName === "SCRIPT") break;
}
console.log("===");
for (const x of it2.iter()) {
console.log(x.tagName);
}
除了 Andrey 的回答之外,如果您想拥有与 Python 脚本中相同的功能,因为在退出循环时生成器无法 re-used,您可以 re-create 每次循环之前的迭代器,并跟踪循环结束的位置以排除对 already-processed 结果的处理,如下所示:
function* iter(a) {
yield* a;
}
var broken = 0;
iterate();
console.log('---');
iterate();
function iterate() {
var it = iter([1, 2, 3, 4, 5]);
for (let x of it) {
if (x <= broken)
continue;
console.log(x);
if (x === 3) {
broken = x;
break;
}
}
}
这更多地与 for..of
的操作方式有关,而不是迭代器的可重用性。如果您要手动拉取迭代器的下一个值,您可以根据需要多次调用它,它会从之前的状态恢复。
这使得这样的事情成为可能:
function* iter(a) {
yield* a;
}
let values = [1, 2, 3, 4, 5];
let it = iter(values)
for (let i = 0, n = values.length; i < n; i++) {
let x = it.next().value
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it) {
console.log(x)
}
对于不依赖于 values
数组的 while
循环也可以这样做:
function* iter(a) {
yield* a;
}
let it = iter([1, 2, 3, 4, 5]),
contin = true
while (contin && (x = it.next().value)) {
console.log(x)
if (x === 3)
contin = false
}
console.log('---')
for (let x of it) {
console.log(x)
}
第二个示例(while
循环)略有不同,因为在条件评估期间分配了 x
。它假定 x
的所有值都是真实的,因此 undefined
可以用作终止条件。如果不是这种情况,则需要在循环块中分配它并且必须设置终止条件。类似于 if(x===undefined)contin=false
或检查迭代器是否已到达其输入的末尾。
正如其他答案所指出的,for..of
在任何情况下都会关闭迭代器,因此需要另一个包装器来保存状态,例如
function iter(a) {
let gen = function* () {
yield* a;
}();
return {
next() {
return gen.next()
},
[Symbol.iterator]() {
return this
}
}
}
it = iter([1, 2, 3, 4, 5]);
for (let x of it) {
console.log(x);
if (x === 3)
break;
}
console.log('---');
for (let x of it) {
console.log(x);
}
根据规范,此行为是预期的,但有一个简单的解决方案。 for..of
循环在循环结束后调用return
method:
Invoking this method notifies the Iterator object that the caller does not intend to make any more next method calls to the Iterator.
解决方案
你当然可以用一个自定义的函数替换那个函数,它不会关闭实际的迭代器,就在循环中使用它之前:
iter.return = value => ({ value, done: true });
示例:
function* iter(a) {
yield* a;
}
it = iter([1, 2, 3, 4, 5])
it.return = () => ({})
for (let x of it) {
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it) {
console.log(x)
}
考虑这个 python 代码
it = iter([1, 2, 3, 4, 5])
for x in it:
print x
if x == 3:
break
print '---'
for x in it:
print x
它打印 1 2 3 --- 4 5
,因为迭代器 it
记住它在循环中的状态。当我在 JS 中做看似相同的事情时,我得到的只是 1 2 3 ---
。
function* iter(a) {
yield* a;
}
it = iter([1, 2, 3, 4, 5])
for (let x of it) {
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it) {
console.log(x)
}
我错过了什么?
不幸的是,JS 中的生成器对象不可重用。 在 MDN
上明确说明Generators should not be re-used, even if the for...of loop is terminated early, for example via the break keyword. Upon exiting a loop, the generator is closed and trying to iterate over it again does not yield any further results.
如前所述,发电机是一次性的。
但是通过将数组包装在闭包中并返回一个新的生成器来模拟 re-usable 迭代器很容易..
例如
function resume_iter(src) {
const it = src[Symbol.iterator]();
return {
iter: function* iter() {
while(true) {
const next = it.next();
if (next.done) break;
yield next.value;
}
}
}
}
const it = resume_iter([1,2,3,4,5]);
for (let x of it.iter()) {
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it.iter()) {
console.log(x)
}
console.log("");
console.log("How about travesing the DOM");
const it2 = resume_iter(document.querySelectorAll("*"));
for (const x of it2.iter()) {
console.log(x.tagName);
//stop at first Script tag.
if (x.tagName === "SCRIPT") break;
}
console.log("===");
for (const x of it2.iter()) {
console.log(x.tagName);
}
除了 Andrey 的回答之外,如果您想拥有与 Python 脚本中相同的功能,因为在退出循环时生成器无法 re-used,您可以 re-create 每次循环之前的迭代器,并跟踪循环结束的位置以排除对 already-processed 结果的处理,如下所示:
function* iter(a) {
yield* a;
}
var broken = 0;
iterate();
console.log('---');
iterate();
function iterate() {
var it = iter([1, 2, 3, 4, 5]);
for (let x of it) {
if (x <= broken)
continue;
console.log(x);
if (x === 3) {
broken = x;
break;
}
}
}
这更多地与 for..of
的操作方式有关,而不是迭代器的可重用性。如果您要手动拉取迭代器的下一个值,您可以根据需要多次调用它,它会从之前的状态恢复。
这使得这样的事情成为可能:
function* iter(a) {
yield* a;
}
let values = [1, 2, 3, 4, 5];
let it = iter(values)
for (let i = 0, n = values.length; i < n; i++) {
let x = it.next().value
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it) {
console.log(x)
}
对于不依赖于 values
数组的 while
循环也可以这样做:
function* iter(a) {
yield* a;
}
let it = iter([1, 2, 3, 4, 5]),
contin = true
while (contin && (x = it.next().value)) {
console.log(x)
if (x === 3)
contin = false
}
console.log('---')
for (let x of it) {
console.log(x)
}
第二个示例(while
循环)略有不同,因为在条件评估期间分配了 x
。它假定 x
的所有值都是真实的,因此 undefined
可以用作终止条件。如果不是这种情况,则需要在循环块中分配它并且必须设置终止条件。类似于 if(x===undefined)contin=false
或检查迭代器是否已到达其输入的末尾。
正如其他答案所指出的,for..of
在任何情况下都会关闭迭代器,因此需要另一个包装器来保存状态,例如
function iter(a) {
let gen = function* () {
yield* a;
}();
return {
next() {
return gen.next()
},
[Symbol.iterator]() {
return this
}
}
}
it = iter([1, 2, 3, 4, 5]);
for (let x of it) {
console.log(x);
if (x === 3)
break;
}
console.log('---');
for (let x of it) {
console.log(x);
}
根据规范,此行为是预期的,但有一个简单的解决方案。 for..of
循环在循环结束后调用return
method:
Invoking this method notifies the Iterator object that the caller does not intend to make any more next method calls to the Iterator.
解决方案
你当然可以用一个自定义的函数替换那个函数,它不会关闭实际的迭代器,就在循环中使用它之前:
iter.return = value => ({ value, done: true });
示例:
function* iter(a) {
yield* a;
}
it = iter([1, 2, 3, 4, 5])
it.return = () => ({})
for (let x of it) {
console.log(x)
if (x === 3)
break
}
console.log('---')
for (let x of it) {
console.log(x)
}