[英]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上明確說明
不應重復使用生成器,即使 for...of 循環提前終止,例如通過 break 關鍵字。 退出循環后,生成器將關閉,並嘗試再次對其進行迭代不會產生任何進一步的結果。
如前所述,發電機是一次性的。
但是通過將數組包裝在一個閉包中並返回一個新的生成器來模擬一個可重用的迭代器是很容易的。
例如。
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); }
這更多地與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
循環在循環結束后調用return
方法:
調用此方法會通知 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) }
除了 Andrey 的回答之外,如果您想擁有與 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; } } }
正如其他答案中所指出的, for..of
在任何情況下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); }
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.