<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] # 21 Iterables 和迭代器 ## 21.1 概述 ES6引入了一種遍歷數據的新機制:迭代。兩個概念是迭代的核心: 1. 可迭代是一種數據結構,讓我們可以方便的訪問其元素。它通過實現一個鍵為 `Symbol.iterator` 的方法來實現。這是迭代器的工廠方法。 2. 迭代器是用于遍歷數據結構元素的指針(想想數據庫中的游標 (cursors ))。 如下是在 TypeScript 中用接口來表示的方式: ```ts interface Iterable { [Symbol.iterator]() : Iterator; } interface Iterator { next() : IteratorResult; } interface IteratorResult { value: any; done: boolean; } ``` ### 21.1.1 可迭代的對象 **可以供 for...of 消費的原生數據結構** * Array * Map * Set * String * **TypedArray(一種通用的固定長度緩沖區類型,允許讀取緩沖區中的二進制數據)** * 函數中的 arguments 對象 * NodeList 對象 為什么原生數據結構中并沒有對象(Object)? 字面量對象是不可迭代的,具體下面會有相關介紹。 那是因為對象屬性的遍歷先后順序是不確定的,需要開發者手動指定。**本質上,遍歷器是一種線性處理,對于任何非線性的數據結構,部署遍歷器接口就等于部署一種線性變換**。 ### 21.1.2 內部構造使用 Iterator 接口 * 通過數組模式進行解構: ```js const [ a , b ] = new Set ([ 'a' , 'b' , 'c' ]); ``` * `for-of`循環: ```js for ( const [ 'a' , 'b' , 'c' ]) { console . log ( x ); } ``` * `Array.from()` : ```js const arr = Array . from ( new Set ([ 'a' , 'b' , 'c' ])); ``` * 展開運算符( `...` ): ```js const arr = [... new Set ([ 'a' , 'b' , 'c' ])]; ``` * Maps 和Sets 的構造器: ```js const map = new Map ([[ false , 'no' ], [ true , 'yes' ]]); const set = new Set ([ 'a' , 'b' , 'c' ]); ``` * `Promise.all()` ,`Promise.race()`: ```js Promise . all ( iterableOverPromises ). then ( ··· ); Promise . race ( iterableOverPromises ). then ( ··· ); ``` * `yield*` : ```js yield * anIterable ; ``` ## 21.2 可迭代性 可迭代性的主要概念如下: * Data consumers(數據消費者):JavaScript具有使用數據的語言結構。例如,`for-of` 循環遍歷值,而 spread 操作符(`…`)將值插入數組或函數調用中。 * Data sources(數據源):數據消費者可以從各種數據源獲取其值。例如,您可能希望迭代數組的元素、Map 中的鍵值條目或字符串的字符。 每個消費者都支持所有來源是不切實際的,特別是因為可以創建新的來源(例如通過庫)。 需要一種統一的接口機制,來處理所有不同的數據結構。 因此,ES6引入了`Iterable` 。 數據消費者使用它,數據源實現它: ![](https://box.kancloud.cn/e634d8d6170a1304e1d65adb591ce1aa_1287x433.jpg =340x120) 因為JS中沒有接口,所以遍歷器(Iterator)更像是一種約定。為各種不同的數據結構提供統一的訪問機制。 任何數據結構只要部署了 Iterator 接口,我們就成這種數據結構為 “可遍歷”(Iterable)。**ES6 規定,默認的 Iterator 接口部署在數據結構的 `Symbol.iterator` 屬性,或者說,一個數據結構只要具有 `Symbol.iterator` 數據,就可以完成遍歷操作(即依次處理該數據結構的所有成員)。 * Source(源):如果某個值的方法的鍵是符號`Symbol.iterator`,它返回一個所謂的迭代器(iterator),則該值被認為是可迭代的(iterable)。 迭代器是一個通過其方法 `next()`返回值的對象。 我們說:它迭代可迭代的項(內容),每次調用每次返回一個值。 * Consumption (消費):數據消費者使用迭代器檢索他們正在使用的值。 現在來看看,數組`arr` 可以如何消費?首先通過 鍵為`Symbol.iterator`的方法,創建一個迭代器: ```js const arr = ['a', 'b', 'c']; const iter = arr[Symbol.iterator](); ``` 然后通過該迭代器的 `next()` 方法重復檢索 該數組中的每個項: ```js > iter.next() { value: 'a', done: false } > iter.next() { value: 'b', done: false } > iter.next() { value: 'c', done: false } > iter.next() { value: undefined, done: true } ``` 可以看到,`next()` 返回的每個項都會被包裝在一個對象中,`value` 值為原數組中的項值,`done` 是否完成了該數組項序列的檢索。 Iterable 和迭代器 是所謂的迭代協議([接口加上使用它們的規則](http://exploringjs.com/es6/ch_about-book.html#sec_protocol))的一部分。該協議的一個關鍵特征是它是順序的:迭代器每次返回一個值。這意味著,如果可迭代數據結構是非線性的(如樹),迭代將使其線性化。 ## 21.3 可迭代數據源 我將使用`for-of`循環(參見章節 [for-of循環](http://exploringjs.com/es6/ch_for-of.html#ch_for-of))迭代各種可迭代數據。 ### 21.3.1 數組 數組(和Typed Arrays)可迭代其元素: ```js for ( const [ 'a' , 'b' ]) { console . log ( x ); } // Output: // 'a' // 'b' ``` ### 21.3.2 字符串 字符串是可迭代的,但它們遍歷Unicode代碼點,每個代碼點可能包含一個或兩個JavaScript字符: ``` for (const x of 'a\uD83D\uDC0A') { console.log(x); } // Output: // 'a' // '\uD83D\uDC0A' (crocodile emoji) ``` 您剛剛看到原始值也可以迭代。所以不是要求一個是對象,才是可迭代的。 這是因為在訪問迭代器方法(屬性鍵`Symbol.iterator`)之前,所有值都被強制轉換為對象。 ### 21.3.3 Maps 映射 是對其條目的迭代。 每個條目編碼為[key,value]對,具有兩個元素的Array。 這些條目總是以確定的方式迭代,其順序與它們被添加到 這個映射時的順序相同。 ``` const map = new Map().set('a', 1).set('b', 2); for (const pair of map) { console.log(pair); } // Output: // ['a', 1] // ['b', 2] ``` 請注意,WeakMaps 不可迭代。 ### 21.3.4 Sets 集合是對其元素的迭代(以與它們添加到集合相同的順序迭代)。 ``` const set = new Set().add('a').add('b'); for (const x of set) { console.log(x); } // Output: // 'a' // 'b' // 'b' ``` 請注意,WeakSets 不可迭代。 ### 21.3.5 `arguments` 盡管特殊變量`arguments`在ECMAScript 6中或多或少已經過時(由于 rest參數),但它是可迭代的: ``` function printArgs() { for (const x of arguments) { console.log(x); } } printArgs('a', 'b'); // Output: // 'a' // 'b' ``` ### 21.3.6 DOM 數據結構 大多數DOM數據結構最終都是可迭代的: ```js for (const node of document.querySelectorAll('div')) { ··· } ``` 請注意,實現此功能正在進行中。 但這樣做相對容易,因為符號`Symbol.iterator` 不會與現有的屬性鍵沖突。 ### 21.3.7 可變計算數據 并非所有可迭代內容都必須來自數據結構,它也可以即時計算。 例如,所有主要的ES6數據結構(Arrays, Typed Arrays, Maps, Sets)都有三個返回可迭代對象的方法: * `entries()` 返回一個可迭代的條目,編碼為[key,value] 的Array。 對于Arrays,值是Array元素,鍵是它們的索引。 對于集合,每個鍵和值都相同 - Set元素。 * `keys()` 返回條目鍵的可迭代值。 * `values()` 返回條目值的可迭代值。 讓我們看看它是什么樣的。 `entries()`為您提供了獲取Array元素及其索引的好方法: ```js const arr = ['a', 'b', 'c']; for (const pair of arr.entries()) { console.log(pair); } // Output: // [0, 'a'] // [1, 'b'] // [2, 'c'] ``` ### 21.3.8 普通對象不可迭代 普通對象(由對象字面量創建)不可迭代: ```js for (const x of {}) { // TypeError console.log(x); } ``` 默認情況下,為什么對象不能在屬性上迭代? 推理如下。 您可以在JavaScript中迭代兩個級別: * 程序級:迭代屬性意味著檢查程序的結構。 * 數據級別:迭代數據結構意味著檢查程序管理的數據。 對屬性進行迭代默認意味著混合這些級別,這將有兩個缺點: * 您無法迭代數據結構的屬性。 * 迭代對象的屬性后,將該對象轉換為數據結構會破壞您的代碼。 如果引擎要通過方法 `Object.prototype[Symbol.iterator]()` 實現迭代,那么還會有一個警告:通過 `Object.create(null)` 創建的對象將不可迭代,因為`Object.prototype`不在他們的原型鏈。 重要的是要記住,如果將[objects 用作Maps](http://exploringjs.com/es6/leanpub-endnotes.html#fn-iteration_1),則迭代對象的屬性大多是有趣的。 但我們只在ES5中這樣做,那時我們沒有更好的選擇。 在ECMAScript 6中,我們有內置的數據結構 `Map`。 #### 21.3.8.1 如何迭代屬性 迭代屬性的正確(和安全)方法是通過工具函數。 例如,通過 `objectEntries()`, 它的實現將在后面顯示(未來的ECMAScript版本可能內置了類似的東西): ```js const obj = { first: 'Jane', last: 'Doe' }; for (const [key,value] of objectEntries(obj)) { console.log(`${key}: ${value}`); } // Output: // first: Jane // last: Doe ``` ## 21.4 迭代語言結構 以下ES6語言構造使用迭代協議: * 通過數組模式進行解構 * `for-of`循環 * `Array.from()` * 展開運算符( `...` ) * Maps 和Sets的構造器 * `Promise.all()`,`Promise.race()` * `yield*` 接下來的部分將詳細介紹 ### 21.4.1 通過數組模式進行解構 通過數組模式進行解構適用于任何可迭代: ```js const set = new Set().add('a').add('b').add('c'); const [x,y] = set; // x='a'; y='b' const [first, ...rest] = set; // first='a'; rest=['b','c']; ``` ### 21.4.2 for-of循環 `for-of` 是ECMAScript 6中的一個新循環。它的基本形式如下所示: ``` for (const x of iterable) { ··· } ``` 有關更多信息,請查看[for-of循環](http://exploringjs.com/es6/ch_for-of.html#ch_for-of)。 請注意,iterable 的 可迭代性是必需的,否則 `for-of` 不能循環值。 這意味著必須將非可迭代值轉換為可迭代的值。 例如,通過`Array.from()`。 ### 21.4.3 `Array.from()` `Array.from()`將可迭代和類似 Array 的值轉換為 Arrays。 它也適用于typed Arrays。 ```js > Array.from(new Map().set(false, 'no').set(true, 'yes')) [[false,'no'], [true,'yes']] > Array.from({ length: 2, 0: 'hello', 1: 'world' }) ['hello', 'world'] ``` 有關`Array.from()`更多信息,請參閱有關[數組的章節](http://exploringjs.com/es6/ch_arrays.html#Array_from) 。 ### 21.4.4 展開運算符( ... ) spread運算符將iterable的值插入到Array中: ```js > const arr = ['b', 'c']; > ['a', ...arr, 'd'] ['a', 'b', 'c', 'd'] ``` 這意味著它為您提供了一種將任何迭代轉換為數組的簡便方式: ```js const arr = [... iterable ]; ``` 展開運算符還將 iterable 轉換為函數,方法或構造函數調用的參數: ```js > Math.max(...[-1, 8, 3]) 8 ``` ### 21.4.5 Maps 和Sets 構造函數 Map的構造函數將 [key,value] 對上的可迭代變為Map: ```js > const map = new Map([['uno', 'one'], ['dos', 'two']]); > map.get('uno') 'one' > map.get('dos') 'two' ``` Set的構造函數將可迭代的元素轉換為Set: ```js > const set = new Set(['red', 'green', 'blue']); > set.has('red') true > set.has('yellow') false ``` WeakMap 和WeakSet 的構造函數的工作方式類似。此外,Maps 和Sets 本身是可迭代的(WeakMaps 和WeakSets 不是),這意味著您可以使用它們的構造函數來克隆它們。 ### 21.4.6 Promises `Promise.all()` 和 `Promise.race()` 接受 Promises上的迭代: ```js Promise.all(iterableOverPromises).then(···); Promise.race(iterableOverPromises).then(···); ``` ### 21.4.7 `yield*` `yield*` 是僅在生成器內可用的運算符。 它產生迭代對象所迭代的所有項。 ```js function* yieldAllValuesOf(iterable) { yield* iterable; } ``` `yield*` 最重要的用例是遞歸調用生成器(生成可迭代的東西)。 ## 21.5 實現迭代 在本節中,我將詳細解釋如何實現iterables。請注意,[ES6生成器](http://exploringjs.com/es6/ch_generators.html#ch_generators)通常比“手動”更方便去實現。 迭代協議如下所示: ![](https://box.kancloud.cn/2d2d447adb878f48229334be57ab8726_1683x370.jpg =420x100) 如果對象具有其鍵為`Symbol.iterator`的方法(自己的或繼承的),則該對象變為可迭代 (“實現” Iterable )。 該方法必須返回一個迭代器 ,一個通過其方法`next()`迭代的“內部” 項的對象。 在 TypeScript 表示中,iterables 和迭代器的接口如下所示: ```js interface Iterable { [Symbol.iterator]() : Iterator; } interface Iterator { next() : IteratorResult; return?(value? : any) : IteratorResult; } interface IteratorResult { value: any; done: boolean; } ``` `return()` 是一個可選的方法,我們將在[以后使用](http://exploringjs.com/es6/leanpub-endnotes.html#fn-iteration_3)。讓我們首先實現一個模擬的迭代,以了解迭代的工作原理。 ```js const iterable = { [Symbol.iterator]() { let step = 0 const iterator = { next() { if (step <= 2) { step++ } switch (step) { case 1: return { value: 'hello', done: false } case 2: return { value: 'world', done: false } default: return { value: undefined, done: true } } } } return iterator } } ``` 讓我們檢查一下, `iterable` 實際上是可迭代的: ```js for (const x of iterable) { console.log(x) } // Output: // hello // world ``` 代碼執行三個步驟,計數器 step 確保一切都以正確的順序發生。 首先,我們返回值'hello' ,然后返回值'world' ,然后我們指示已經迭代結束。 每個項目都包含在一個具有以下屬性的對象中: - `value` 保存實際值 - `done` 是一個布爾標志,指示是否已到達終點 如果為`false`,則可以省略`done`;如果`undefined`,則可以省略`value`。 也就是說,`switch`語句可以寫成如下。 ```js switch (step) { case 1: return { value: 'hello' } case 2: return { value: 'world' } default: return { done: true } } ``` 正如 生成器的章節中所解釋的那樣,在某些情況下,您甚至需要最后一項`done: true`才能獲得 value。 否則,`next()`可以更簡單并直接返回項目(不將它們包裝在對象中)。 然后通過特殊值(例如,一個 symbol)指示迭代的結束。 讓我們再看一個可迭代的實現。 函數 `iterateOver()` 在通過傳遞給它的參數,返回一個 iterable: ```js function iterateOver(...args) { let index = 0 const iterable = { [Symbol.iterator]() { const iterator = { next() { if (index < args.length) { return { value: args[index++] } } else { return { done: true } } } } return iterator } } return iterable } // Using `iterateOver()`: for (const x of iterateOver('fee', 'fi', 'fo', 'fum')) { console.log(x) } // Output: // fee // fi // fo // fum ``` ### 21.5.1 可迭代的迭代器 如果可迭代對象 和迭代器是同一個對象,則可以簡化前一個函數: ```js function iterateOver(...args) { let index = 0 const iterable = { [Symbol.iterator]() { return this }, next() { if (index < args.length) { return { value: args[index++] } } else { return { done: true } } } } return iterable } ``` 即使原始的 iterable 和迭代器不是同一個對象,如果迭代器具有以下方法(這也使它成為可迭代的),它偶爾會有用: ```js [Symbol.iterator]() { return this; } ``` 所有內置的 ES6 迭代器都遵循這種模式(通過一個通用的原型,參見有關生成器的章節 )。 例如,Arrays 的默認迭代器: ```js > const arr = []; > const iterator = arr[Symbol.iterator](); > iterator[Symbol.iterator]() === iterator > true ``` 如果迭代器也是可迭代的,那么它有什么用呢? `for-of` 僅適用于 iterables,不適用于迭代器。 因為 Array 迭代器是可迭代的,所以可以在另一個循環中繼續迭代: ```js const arr = ['a', 'b'] const iterator = arr[Symbol.iterator]() for (const x of iterator) { console.log(x) // a break } // Continue with same iterator: for (const x of iterator) { console.log(x) // b } ``` 繼續迭代的一個用例是,您可以在通過 `for-of` 處理實際內容之前刪除初始項(例如標題)。 ### 21.5.2 可選的迭代器方法: `return()`和 `throw()` 兩個迭代器方法是可選的: - 如果迭代過早結束,則 `return()`為迭代器提供清理的機會。 - `throw()`是關于將方法調用轉發給通過 `yield*` 迭代的生成器。 有關[生成器的章節](http://exploringjs.com/es6/ch_generators.html#ch_generators)對此進行了解釋。 #### 21.5.2.1 通過 `return()` 關閉迭代器 如前所述,可選的迭代器方法 `return()` 是關于如果迭代器沒有迭代直到結束 而讓迭代器清理的。 它關閉了一個迭代器。 在 for-of 循環中,過早(或突然 ,在規范語言中)終止可能由以下原因引起: - break - continue (如果您繼續外部循環,則 `continue` 的作用類似于 `break`) - throw - return 在每種情況下, `for-of` 讓迭代器知道循環不會完成。 讓我們看一個例子,一個函數 `readLinesSync` ,它返回一個文件中的可迭代文本行,并且無論發生什么都想關閉該文件: ```js function readLinesSync(fileName) { const file = ···; return { ··· next() { if (file.isAtEndOfFile()) { file.close(); return { done: true }; } ··· }, return() { file.close(); return { done: true }; }, }; } ``` 由于 `return()`,文件將在以下循環中正確關閉: ```js // Only print first line for (const line of readLinesSync(fileName)) { console.log(x) break } ``` `return()`方法必須返回一個對象。這是由于生成器處理 `return` 語句的方式造成的,有關[生成器的章節](http://exploringjs.com/es6/ch_generators.html#ch_generators)將對此進行解釋。 以下構造關閉未完全“耗盡”的迭代器: - for-of - yield\* - Destructuring - Array.from() - Map(), Set(), WeakMap(), WeakSet() - Promise.all(), Promise.race() [稍后的部分](http://exploringjs.com/es6/ch_iteration.html#sec_closing-iterators)將提供關于關閉迭代器的更多信息。 ## 21.6 可迭代的更多例子 在本節中,我們將看一些可迭代的例子。 大多數這些迭代更容易通過生成器實現。關于[生成器的一章](http://exploringjs.com/es6/ch_generators.html#ch_generators)展示了如何實現。 ### 21.6.1 返回 iterables 的工具函數 返回可迭代的工具函數和方法與可迭代數據結構一樣重要。 以下是用于迭代對象的自身屬性的工具函數。 ```js function objectEntries(obj) { let index = 0 // In ES6, you can use strings or symbols as property keys, // Reflect.ownKeys() retrieves both const propKeys = Reflect.ownKeys(obj) return { [Symbol.iterator]() { return this }, next() { if (index < propKeys.length) { const key = propKeys[index] index++ return { value: [key, obj[key]] } } else { return { done: true } } } } } const obj = { first: 'Jane', last: 'Doe' } for (const [key, value] of objectEntries(obj)) { console.log(`${key}: ${value}`) } // Output: // first: Jane // last: Doe ``` 另一種選擇是使用迭代器而不是索引來遍歷具有屬性鍵的數組: ```js function objectEntries(obj) { let iter = Reflect.ownKeys(obj)[Symbol.iterator]() return { [Symbol.iterator]() { return this }, next() { let { done, value: key } = iter.next() if (done) { return { done: true } } return { value: [key, obj[key]] } } } } ``` ### 21.6.2 迭代的組合器 [組合器](http://exploringjs.com/es6/leanpub-endnotes.html#fn-iteration_4) 是組合現有迭代(iterables)來創建新迭代的函數。 #### 21.6.2.1 `take(n, iterable)` 讓我們從組合函數 `take(n, iterable)`,它返回可迭代的前 `n` 項的 `iterable`。 ```js function take(n, iterable) { const iter = iterable[Symbol.iterator]() return { [Symbol.iterator]() { return this }, next() { if (n > 0) { n-- return iter.next() } else { return { done: true } } } } } const arr = ['a', 'b', 'c', 'd'] for (const x of take(2, arr)) { console.log(x) } // Output: // a // b ``` 這個版本的 `take()` 不會關閉迭代器 `iter`。在我解釋了關閉迭代器的實際含義之后,稍后將展示如何做到這一點。 #### 21.6.2.2 `zip(...iterables)` `zip` 將 `n` 個可迭代項轉換為 `n` 元組(編碼為長度為 `n` 的數組)的可迭代項。。 ```js function zip(...iterables) { const iterators = iterables.map(i => i[Symbol.iterator]()) let done = false return { [Symbol.iterator]() { return this }, next() { if (!done) { const items = iterators.map(i => i.next()) done = items.some(item => item.done) if (!done) { return { value: items.map(i => i.value) } } // Done for the first time: close all iterators for (const iterator of iterators) { if (typeof iterator.return === 'function') { iterator.return() } } } // We are done return { done: true } } } } ``` 如您所見,最短的 `iterable` 決定了結果的長度: ```js const zipped = zip(['a', 'b', 'c'], ['d', 'e', 'f', 'g']) for (const x of zipped) { console.log(x) } // Output: // ['a', 'd'] // ['b', 'e'] // ['c', 'f'] ``` ### 21.6.3 無限可迭代 有些迭代可能永遠不會 done 。 ```js function naturalNumbers() { let n = 0 return { [Symbol.iterator]() { return this }, next() { return { value: n++ } } } } ``` 對于無限迭代,您一定不要去遍歷它的所有項。例如,通過從 `for-of` 循環中斷開 ```js for (const x of naturalNumbers()) { if (x > 2) break //這里進行中斷 console.log(x) } ``` 或者只訪問無限可迭代的開頭: ```js const [a, b, c] = naturalNumbers() // a=0; b=1; c=2; ``` 或者使用組合器。`take()` 是一種可能性: ```js for (const x of take(3, naturalNumbers())) { console.log(x) } // Output: // 0 // 1 // 2 ``` `zip()`返回的 iterable 的“長度”由其最短的輸入可迭代決定。 這意味著 `zip()` 和 `naturalNumbers()` 為您提供了對任意(有限)長度的迭代器進行編號的方法: ```js const zipped = zip(['a', 'b', 'c'], naturalNumbers()) for (const x of zipped) { console.log(x) } // Output: // ['a', 0] // ['b', 1] // ['c', 2] ``` ## 21.7 FAQ:iterables 和 iterators ### 21.7.1 迭代協議不是很慢嗎? 您可能會擔心迭代協議很慢,因為每次調用 next()都會創建一個新對象。然而,對于小對象的內存管理在現代引擎中是快速的,從長遠來看,引擎可以優化迭代,這樣就不需要分配中間對象。關于 es-discuss 上的[一個帖子](https://esdiscuss.org/topic/performance-of-iterator-next-as-specified)有更多的信息。 ### 21.7.2 我可以多次重復使用同一個對象嗎? 原則上,沒有什么能阻止迭代器多次重復使用相同的迭代結果對象 - 我希望大多數事情都能正常工作。 但是,如果客戶端緩存迭代結果,則會出現問題: ```js const iterationResults = [] const iterator = iterable[Symbol.iterator]() let iterationResult while (!(iterationResult = iterator.next()).done) { iterationResults.push(iterationResult) } ``` 如果迭代器重用其迭代結果對象,則 `iterationResults` 通常會多次包含同一個對象。 ### 21.7.3 為什么 ECMAScript 6 沒有可迭代的組合器? 您可能想知道為什么 ECMAScript 6 沒有可迭代的組合器 ,用于處理迭代的工具或用于創建迭代的工具。 那是因為計劃分兩步進行: - 第 1 步:標準化迭代協議。 - 第 2 步:根據該協議等待庫。 最終,一個這樣的庫或來自幾個庫的片段將被添加到 JavaScript 標準庫中。 如果您想了解這樣的庫可能是什么樣子,請查看標準 Python 模塊 `itertools`。 ### 21.7.4 難道迭代(iterables)很難實現嗎? 是的,迭代很難實現 - 如果你手動去實現它們。 下一章將介紹有助于完成此任務的[生成器](http://exploringjs.com/es6/ch_generators.html#ch_generators) (以及其他內容)。 # 21.8 深入的 ECMAScript 6 迭代協議 迭代協議包含以下接口(我省略了 Iterator 中的 `throw()`,它只受 `yield*` 支持,并且是可選的): ```js interface Iterable { [Symbol.iterator]() : Iterator; } interface Iterator { next() : IteratorResult; return?(value? : any) : IteratorResult; } interface IteratorResult { value : any; done : boolean; } ``` 規范有一個[關于迭代協議的部分](http://www.ecma-international.org/ecma-262/6.0/#sec-iteration) 。 ## 21.8.1 迭代 `next()` 規則: - 只要迭代器仍然具有要生成的值 `x` , `next()`返回對象`{ value: x, done: false }`。 - 迭代完最后一個值之后, `next()` 應該總是返回一個屬性為 true 的對象。 ### 21.8.1.1 IteratorResult 迭代器結果的屬性不必是 `true` 或 `false`,能夠代表真假進行判斷就是足夠的。所有內置語言機制都允許您省略 done: false 。 ### 21.8.1.2 返回新迭代器的 Iterables 與總是返回相同迭代器的 Iterables 一些 iterables 每次被要求生成一個新的迭代器。 例如,數組: ```js function getIterator(iterable) { return iterable[Symbol.iterator]() } const iterable = ['a', 'b'] console.log(getIterator(iterable) === getIterator(iterable)) // false ``` 其他迭代每次都返回相同的迭代器。 例如,生成器對象: ```js function* elements() { yield 'a' yield 'b' } const iterable = elements() console.log(getIterator(iterable) === getIterator(iterable)) // true ``` 當您多次迭代同一迭代器時,可迭代(iterable)是否產生新的迭代器并不重要。例如,通過以下函數: ```js function iterateTwice(iterable) { for (const x of iterable) { console.log(x) } for (const x of iterable) { console.log(x) } } ``` 使用新的迭代器,您可以多次迭代相同的可迭代: ```js iterateTwice(['a', 'b']) // Output: // a // b // a // b ``` 如果每次都返回相同的迭代器,則不能: ```js iterateTwice(elements()) // Output: // a // b ``` 請注意,標準庫中的每個迭代器也是可迭代的。 它的方法`[Symbol.iterator]()`返回 `this`,這意味著它總是返回相同的迭代器(本身)。 ### 21.8.2 關閉迭代器 迭代協議區分了兩種完成迭代器的方法: - 耗盡(Exhaustion):完成迭代器的常規方法是檢索其所有值。也就是說,一直調用 `next()` 直到它返回一個屬性`done`為 `true` 的對象。 - 關閉(Closing):通過調用 `return()`,告訴迭代器你不打算再調用 next() 。 調用 `return()`規則: - `return()` 是一個可選方法,并非所有迭代器都有它。 具有它的迭代器被稱為可關閉的 。 - 只有在迭代器沒有用盡時才應該調用 `return()` 。 例如,只要“突然”(在它完成之前 `return()`,`for-of` 調用 `return()`)。 以下操作會導致突然退出:`break`,`continue`(帶有外部塊的標簽), `return` , `throw`。 實現 `return()`規則: - 方法調用 `return(x)` 通常應該生成對象 `{ done: true, value: x }`,但是如果結果不是對象,語言機制只會拋出錯誤([source in spec](http://www.ecma-international.org/ecma-262/6.0/#sec-iteratorclose))。 - 調用 `return()`后,`next()` 返回的對象也應該 `done`。 下面的代碼說明了 `for-of` 循環 如果在收到 一個 `done` 迭代器結果 之前中止它,則它調用 `return()`。 也就是說,如果在收到最后一個值后中止,則甚至會調用 return() 。 這是微妙的,當您手動迭代或實現迭代器時,您必須小心謹慎。 ```js function createIterable() { let done = false const iterable = { [Symbol.iterator]() { return this }, next() { if (!done) { done = true return { done: false, value: 'a' } } else { return { done: true, value: undefined } } }, return() { console.log('return() was called!') } } return iterable } for (const x of createIterable()) { console.log(x) // There is only one value in the iterable and // we abort the loop after receiving it break } // Output: // a // return() was called! ``` #### 21.8.2.1 可關閉的迭代器 如果迭代器具有方法 `return()` 則它是可關閉的。并非所有迭代器都可以關閉。例如,Array 迭代器不是: ```js > let iterable = ['a', 'b', 'c']; > const iterator = iterable[Symbol.iterator](); > 'return' in iterator false ``` 默認情況下,Generator 對象是可關閉的。 例如,由以下生成器函數返回的: ```js function* elements() { yield 'a' yield 'b' yield 'c' } ``` 如果在 `elements()`的結果上調用 `return()`,則迭代完成: ```js > const iterator = elements(); > iterator.next() { value: 'a', done: false } > iterator.return() { value: undefined, done: true } > iterator.next() { value: undefined, done: true } ``` 如果迭代器不可關閉,則可以在 `for-of` 循環中突然退出(例如 A 行中的那個)之后繼續迭代它: ```js function twoLoops(iterator) { for (const x of iterator) { console.log(x) break // (A) } for (const x of iterator) { console.log(x) } } function getIterator(iterable) { return iterable[Symbol.iterator]() } twoLoops(getIterator(['a', 'b', 'c'])) // Output: // a // b // c ``` 相反, `elements()`返回一個可關閉的迭代器,而 `twoLoops()`內的第二個循環沒有任何可迭代的東西: ```js function* elements() { yield 'a' yield 'b' yield 'c' } function twoLoops(iterator) { for (const x of iterator) { console.log(x) break // (A) } for (const x of iterator) { console.log(x) } } twoLoops(elements()) // Output: // a ``` #### 21.8.2.2 防止迭代器被關閉 以下類是防止迭代器被關閉的通用解決方案。它通過包裝迭代器和轉發除 `return()`之外的所有方法調用來實現這一點。 ```js class PreventReturn { constructor(iterator) { this.iterator = iterator } /** Must also be iterable, so that for-of works */ [Symbol.iterator]() { return this } next() { return this.iterator.next() } return(value = undefined) { return { done: false, value } } // Not relevant for iterators: `throw()` } ``` 如果我們使用 `PreventReturn`,那么在 `twoLoops()` 的第一個循環中突然退出后,生成器 `elements()` 的結果將不會被關閉。 ```js function* elements() { yield 'a' yield 'b' yield 'c' } function twoLoops(iterator) { for (const x of iterator) { console.log(x) break // abrupt exit } for (const x of iterator) { console.log(x) } } twoLoops(elements()) // Output: // a twoLoops(new PreventReturn(elements())) // Output: // a // b // c ``` 還有另一種使生成器不可關閉的方法:生成器函數 `elements()`生成的所有生成器對象都具有原型對象 `elements.prototype` 。 通過 `elements.prototype`,您可以隱藏 `return()`的默認實現(它駐留在 `elements.prototype` 的原型中),如下所示: ```js // Make generator object unclosable // Warning: may not work in transpilers elements.prototype.return = undefined twoLoops(elements()) // Output: // a // b // c ``` #### 21.8.2.3 通過 `try-finally` 對生成器進行清理 一些生成器需要在迭代完成后清理(釋放已分配的資源,關閉打開的文件等)。這就是我們實現它的方式: ```js function* genFunc() { yield 'a' yield 'b' console.log('Performing cleanup') } ``` 在正常的 for-of 循環中,一切都很好: ```js for (const x of genFunc()) { console.log(x) } // Output: // a // b // Performing cleanup ``` 但是,如果在第一次 yield 后退出循環,則執行似乎永遠停留在那里并且永遠不會到達清理步驟: ```js for (const x of genFunc()) { console.log(x) break } // Output: // a ``` 實際發生的情況是,每當提前離開 `for-of` 循環時,`for-of` 都會向當前迭代器發送 `return()`。這意味著沒有完成清理步驟,因為生成器函數就提前返回。 值得慶幸的是,通過在 `finally` 子句中執行清理可以很容易地解決這個問題: ```js function* genFunc() { try { yield 'a' yield 'b' } finally { console.log('Performing cleanup') } } ``` 現在一切都按預期工作: ```js for (const x of genFunc()) { console.log(x) break } // Output: // a // Performing cleanup ``` 因此,使用需要以某種方式關閉或清理的資源的一般模式是: ```js function* funcThatUsesResource() { const resource = allocateResource(); try { ··· } finally { resource.deallocate(); } } ``` #### 21.8.2.4 在手動實現的迭代器中處理清理 ```js const iterable = { [Symbol.iterator]() { function hasNextValue() { ··· } function getNextValue() { ··· } function cleanUp() { ··· } let returnedDoneResult = false; return { next() { if (hasNextValue()) { const value = getNextValue(); return { done: false, value: value }; } else { if (!returnedDoneResult) { // Client receives first `done` iterator result // => won’t call `return()` cleanUp(); returnedDoneResult = true; } return { done: true, value: undefined }; } }, return() { cleanUp(); } }; } } ``` 注意,當您第一次返回一個`done` 迭代器結果時,必須調用 `cleanUp()`。您不能提前地執行,因為 `return()` 可能仍然會被調用。為了做到這一點,可能很棘手。 #### 21.8.2.5 關閉你使用的迭代器 如果使用迭代器,則應正確關閉它們。在生成器中,您可以讓 for-of 所有工作為您完成: ```js /** * Converts a (potentially infinite) sequence of * iterated values into a sequence of length `n` */ function* take(n, iterable) { for (const x of iterable) { if (n <= 0) { break // closes iterable } n-- yield x } } ``` 如果您手動去管理,需要做一些工作: ```js function* take(n, iterable) { const iterator = iterable[Symbol.iterator]() while (true) { const { value, done } = iterator.next() if (done) break // exhausted if (n <= 0) { // Abrupt exit maybeCloseIterator(iterator) break } yield value n-- } } function maybeCloseIterator(iterator) { if (typeof iterator.return === 'function') { iterator.return() } } ``` 如果不使用生成器,則需要做更多工作: ```js function take(n, iterable) { const iter = iterable[Symbol.iterator]() return { [Symbol.iterator]() { return this }, next() { if (n > 0) { n-- return iter.next() } else { maybeCloseIterator(iter) return { done: true } } }, return() { n = 0 maybeCloseIterator(iter) } } } ``` ## 21.8.3 清單 - 記錄可迭代的:提供以下信息。 - 它是每次返回新的迭代器還是相同的迭代器? - 它的迭代器是可關閉的嗎? - 實現迭代器: - 如果迭代器耗盡或被 `return()`調用,則必須進行清理活動。 - 在生成器中,`try-finally` 您可以在一個位置處理這兩種情況。 - 通過`return()` 關閉迭代器后,它不應該通過 `next()` 生成任何迭代器結果。 - 手動使用迭代器(通過 `for-of` 等): - 不要忘記通過 `return` 關閉迭代器,當且僅當您沒有耗盡它時。要做到這一點可能很棘手。 - 在突然退出后繼續在迭代器上迭代:迭代器必須是不可關閉的或不可關閉的(例如通過工具類)。 > [ES6 迭代器 (Iterator) 和 for...of 循環使用方法]([https://www.jianshu.com/p/3bb77516fa7e](https://www.jianshu.com/p/3bb77516fa7e))
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看