for..of和迭代器状态

时间:2018-03-26 13:34:51

标签: javascript iterator yield

考虑这个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)
}




我错过了什么?

6 个答案:

答案 0 :(得分:6)

不幸的是,JS中的生成器对象不可重用。 在MDN

上明确说明
  

不应重复使用生成器,即使循环的for ...也是如此   提前终止,例如通过break关键字。退出时   循环,生成器关闭,并尝试再次迭代它   不会产生任何进一步的结果。

答案 1 :(得分:3)

如上所述,发电机是一次性的。

但是通过将数组包装在一个闭包中并返回一个新的生成器来模拟一个可重用的迭代器很容易。

例如

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);
}

答案 2 :(得分:2)

此行为符合规范,但有一个简单的解决方案。 for..of 循环在循环结束后调用 return method

<块引用>

调用此方法会通知 Iterator 对象,调用者不打算再对 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)
}

答案 3 :(得分:1)

这与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)
}

对于不依赖于while数组的values循环也可以这样做:

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或检查迭代器是否已到达其输入的末尾。

答案 4 :(得分:0)

除了Andrey的答案之外,如果你想拥有与Python脚本相同的功能,因为在退出循环时无法重新使用生成器,你可以重新创建迭代器在每次循环之前并跟踪循环最终被破坏的位置以排除处理已处理的结果,如下所示:

&#13;
&#13;
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;
    }
  }
}
&#13;
&#13;
&#13;

答案 5 :(得分:0)

正如其他答案中所指出的,for..of在任何情况下都会关闭迭代器,因此需要另一个保存状态所需的包装器,例如

&#13;
&#13;
function iter(a) {
    let it = function* () {
        yield* a;
    }();

    return {
        * [Symbol.iterator]() {
            while (1) {
                let r = it.next();
                if (!r.done)
                    yield r.value;
                else
                    break;
            }
        }
    }
}


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);
}
&#13;
&#13;
&#13;