迭代器
遍歷器(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 }
,但不能使用箭頭函式來建立生成器。
這裡僅僅介紹了生成器可以返回迭代器,但它主要的作用是流程管理,因此可以使用同步的方式書寫非同步程式碼,generator詳情
可迭代物件
(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
封裝了next
的重複呼叫過程,來自動執行迭代器,遍歷訪問可迭代物件的成員。它通過物件的Symbol.iterator
方法來獲取迭代器,並在每次迴圈中呼叫可迭代物件的next
方法,將返回值根據迭代器的規則賦給中間變數,一直迴圈到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]]
複製程式碼
由於展開運算子可以作用於任意可迭代物件,因此要將可迭代物件裝換成陣列,這是最簡單的方法