【问题标题】:for..of and the iterator statefor..of 和迭代器状态
【发布时间】:2018-09-04 16:13:47
【问题描述】:

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

我错过了什么?

【问题讨论】:

标签: javascript iterator yield


【解决方案1】:

不幸的是,JS 中的生成器对象不可重用。 MDN上明确说明

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

【讨论】:

【解决方案2】:

如上所述,生成器是一次性的。

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

例如。

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

【讨论】:

  • 很好,但我希望迭代器不知道底层类型,这不是数组所必需的。
  • @georg 哦,在这种情况下,如何将一个可迭代对象包装在一个闭包中,.. 更新了 sn-p 以处理任何可迭代对象..
  • @georg 更新了 sn-p 来遍历 DOM 节点,因为那不是一个数组,基本上停在第一个 SCRIPT 标记处,然后它再次恢复..
【解决方案3】:

除了安德烈的回答,如果你想拥有与 Python 脚本中相同的功能,由于在退出循环时无法重用生成器,你可以在遍历每个之前重新创建迭代器时间并跟踪循环最终被破坏的位置,以排除对已处理结果的处理,如下所示:

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

【讨论】:

  • 你还在循环两次。
【解决方案4】:

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

【讨论】:

  • 好主意,我已经发布了一个相同的包装器(手动从迭代器中提取值,从而保留其状态)。
【解决方案5】:

正如其他答案中指出的那样,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);
}

【讨论】:

    【解决方案6】:

    根据规范,这种行为是预期的,但有一个简单的解决方案。 for..of循环在循环结束后调用return method

    调用此方法会通知 Iterator 对象,调用者不打算再对 Iterator 进行任何 next 方法调用。

    解决方案

    您当然可以在循环使用它之前,用一个不会关闭实际迭代器的自定义函数替换该函数:

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

    【讨论】:

    • 这太棒了!我不知道return
    猜你喜欢
    • 2014-05-08
    • 2019-09-17
    • 2017-08-06
    • 2017-06-05
    • 2020-06-27
    • 2018-03-19
    • 2018-09-06
    • 2017-02-08
    相关资源
    最近更新 更多