深入理解ES6 ---- 迭代器和生成器
遍歷器(Iterator)是一種介面,為各種不同的資料結構提供統一的訪問機制 。任何資料結構只要部署 Iterator 介面,就可以完成遍歷操作 (即依次處理該資料結構的所有成員)。
(1)Iterator
的作用
- 為各種資料結構,提供一個統一的、簡便的訪問介面
- 使得資料結構的成員能夠按某種次序排列
- ES6 創造了一種新的遍歷命令for...of迴圈,Iterator 介面主要供for...of消費。
(2)Iterator
的迭代過程
所有的迭代器物件都會有一個next
方法,每次呼叫都會返回一個物件:{done: boolean, value: any}
。value
表示當前成員的值,done
表示是否還有更多的資料。迭代器內部會維護一個指標,指向當前成員的位置,每次呼叫next
都會指向下一個成員。
// es5實現迭代器 function creatIterator(arr){ let index = 0; return { next: () =>{ return { done: index > arr.length - 1, value: this.done ? undefined : arr[index++] } } } } const iterator = creatIterator([1, 2, 3]) console.log(a.next())// { done: false, value: 1 } console.log(a.next())// { done: false, value: 2 } console.log(a.next())// { done: false, value: 3 } console.log(a.next())// { done: true, value: undefined } // 之後的呼叫都會返回相同的內容 console.log(a.next())// { done: true, value: undefined } 複製程式碼
生成器
生成器是一種返回迭代器的函式。通過function
後面的*
來表明它是一個生成器。
-
yield
關鍵字是es6
的新特性, 它可以指定呼叫next
方法時的返回值以及呼叫順序 。 -
每當執行完
yield
語句,函式就會停止執行,直到再次呼叫next
方法才會繼續執行 -
yield
關鍵字只能在生成器內部使用 ,其他地方會導致語法錯誤
function * createIterator(){ yield 1 yield 2 } // 生成器返回的是一個迭代器 const iterator = createIterator() console.log(iterator .next())// { done: false, value: 1 } console.log(iterator .next())// { done: false, value: 2 } console.log(iterator .next())// { done: true, value: undefined } 複製程式碼
使用函式表示式:const createIterator = function *(){ yield 1 }
,但不能使用箭頭函式來建立生成器。
可迭代物件
(1)Symbol.iterator
一個數據結構只要具有Symbol.iterator
屬性,就可以認為是可迭代的
-
所有的集合物件
array、set、map
以及string
都是可迭代物件,都有預設的迭代器 ,即Symbol.iterator
屬性 -
生成器生成的物件就是可迭代的,生成器會預設為他們的
Symbol.iterator
屬性賦值 -
通過***執行
Symbol.iterator
方法*** 來獲取物件的迭代器
訪問陣列的預設迭代器:
const arr = [1, 2, 3] // 通過執行陣列的 `Symbol.iterator`方法來獲取 `arr` 的迭代器 const arrIterator = arr[Symbol.iterator]() console.log(arrIterator.next())// { value: 1, done: false } console.log(arrIterator.next())// { value: 2, done: false } console.log(arrIterator.next())// { value: 3, done: false } console.log(arrIterator.next())// { value: undefined, done: true } 複製程式碼
(2)for...of
for...of
通過物件的Symbol.iterator
方法來獲取迭代器,並在每次迴圈中呼叫可迭代物件的next
方法,將迭代器返回物件中的value
賦給中間變數,一直迴圈到done
屬性為true
。
const arr = [1, 2, 3] for(let item of arr){ console.log(item) } // 1 2 3 複製程式碼
(3) 建立可迭代物件
可以通過給一個不可迭代物件設定Symbol.iterator
屬性,使它變成可迭代的。Symbol.iterator
必須是迭代器生成函式
,否則使用for...of
會報錯。
// es5 const list = { items: [1,2,3], [Symbol.iterator](){ let index = 0; return { next: () =>{ return { done: index > this.items.length - 1, value: this.done ? undefined : this.items[index++] } } } } } for (const item of list) { console.log(item) } // 1 2 3 // es6 const list = { items: [1,2,3], *[Symbol.iterator](){ for(item of this.items){ yield item } } } for (const item of list) { console.log(item) } // 1 2 3 複製程式碼
(3) 內建迭代器
entries、values、keys
都是es6
新增的迭代器。下表為各種集合型別的各種迭代器在for...of
迴圈中的中間變數:
集合型別/迭代器 |
entries
|
values
|
keys
|
---|---|---|---|
array
|
[[index, value]]
|
[value]
✔ |
[index]
|
map
|
[[key, value]]
✔ |
[value]
|
[key]
|
set
|
[[value, value]]
|
[value]
✔ |
[value]
|
每個集合型別都有預設的迭代器,在for... of
迴圈中,如果沒有顯式的指定,則使用預設的迭代器。上表中的✔表示各集合型別的for... of
預設迭代器
const arr = [1,2,3] // 沒有顯示指定迭代器,則使用array預設的迭代器,即values迭代器 for (const item of arr) { console.log(item) } // 1 2 3 // 使用顯示指定的entries迭代器 for (const item of arr.entries()) { console.log(item) } // [0,1][1,2][2,3] 複製程式碼
需要注意的是:
entries、values、keys
請勿與Object.entries、Object.values、Obejct.keys
混淆
。前者返回的是
迭代器,用於for...of
迴圈
;後者返回的是根據原物件格式化後的陣列
。
(4) 字串迭代器
在es5
中也可以使用for
迴圈遍歷字串,但卻
以編碼為單元而非字元,因此無法正確訪問雙位元組字元
。es6
中全面支援unicode
,所以**for...of
迴圈就可以正確遍歷字串中的雙位元組字元**
const str = '1' for (let i = 0; i < text.length; i++) { console.log(text[i]); } // " " // " " // "1" // 可以正確識別雙位元組字元 for (let i of text) { console.log(i); } // "" // "1" 複製程式碼
(5)NodeList
迭代器
DOM
標準中有一個NodeList
型別,用於表示頁面文件元素的集合。它
含有length屬性;可以通過[number]
訪問元素
,跟陣列的格式和操作方法很類似,但其實它是物件,也就是我們俗稱的偽陣列
,比如:{0: domObject, 1: domObject, 2: domObject, length:3}
。es6
中NodeList
型別也擁有了預設迭代器,可以使用for...of
迭代
const nodeList = document.getElementsByClassName('abc') for (const node of nodeList) { console.log(node) } 複製程式碼
(6) 展開運算子
展開運算子可以操作所有可迭代物件,並根據預設迭代器來選去要引用的值,然後讀取所有值
const nodeList = document.getElementsByClassName('abc') console.log([...nodeList]) // [node, node, node] const map = new Map() map.set('name','li yang') map.set('age', 18) console.log([...map])// 呼叫了map的預設迭代器 entries // [["name", "li yang"], ["age", 18]] 複製程式碼