
📢 大家好,我是小江同學,本文將會帶你理解
ES6中的迭代器,
發現問題
在
ES6中提出迭代器模式之前,傳統迭代存在著怎樣的問題?為什么要新增迭代器概念呢?
我們先來看幾個例子
let arr = ['小', '丞', '呀']
這是一個簡單的陣列,如果要獲取它的每一項資料,我們可以采用 for 回圈,當然也可以采用 forEach 回圈,這樣很酷
關于
forEach回圈在之前的文章有解釋,原文連接

當純這樣還沒什么問題
我們再看下面的例子,將給定字串單個字符輸出
let str = '011010'

可以采用 for 回圈和 for...in 回圈
問題就這樣出現了
上面兩個例子中我們的目的都只是遍歷,但是卻需要去考慮采用不同的遍歷方式
在第一段代碼中我們遍歷的是一個陣列,第二段遍歷的是一個字串,我們采用了不同的方法,也就是說我們在面對不同資料結構時往往會采取不同的遍歷方式,
在 JavaScript 中原有的表示“集合”的資料結構,主要是 Array 和 Object ,而在 ES6中又新增了 Map和 Set 兩種,同時我們還可以組合使用這些資料結構,面對如此眾多的資料結構,我們卻不能使用一個統一的遍歷方法來獲取資料,這就是所存在的問題!
當然在 ES6 中提供了一個全新的遍歷方法 for...of回圈,但是 for...of 有一個非常重要的地方
“只能對實作了 iterator 介面的物件進行遍歷取值”
所以說 for...of就只是 iterator 雇傭的打工仔,也叫語法糖
了解了先前遍歷方式存在的問題,下面主角登場,帶著問題,
for...of回圈是如何實作統一遍歷的?繼續往下看~
Iterator 迭代器
Iterator 是一種介面,為各種不同的資料結構提供統一的訪問機制,任何資料結構只要部署 Iterator 介面,就可以完成遍歷操作,
1. Iterator 的作用
-
為各種資料結構,提供一個統一的、簡便的訪問介面
-
使得資料結構的成員能夠按某種次序排列
-
ES6創造了一種新的遍歷命令for...of回圈,Iterator介面主要供for...of消費,
2. Iterator 的作業原理
- 創建一個指標物件,指向當前資料結構的起始位置
- 第一次呼叫
next方法時,指標指向資料結構的第一個成員 - 接下來呼叫
next方法,指標后移,直到指向最后一個成員
每次呼叫 next 方法都會回傳一個值,這個值是一個 object,包含兩個屬性,value 和 done,value表示具體的回傳值,done 是布爾型別的,表示集合是否遍歷完成,完成則回傳 true,否則回傳 false,
3. 手寫實作 iterator 介面
function myIterator(arr) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < arr.length ? {
value: arr[nextIndex++],
done: false
} : {
value: undefined,
done: true
}
}
}
}
let arr = [1,2,'ljc']
let it = myIterator(arr)
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

我們來分析一下上面的代碼,myIterator 函式是一個遍歷器生成函式,它的作用是回傳一個遍歷器物件,也是指標物件,
我們通過 next 方法來移動指標,next 方法內部通過閉包來保存指標nextIndex的值,每次呼叫 next 方法 nextIndex都會 +1 ,然后根據nextIndex 的值從陣列內取出資料作為 value ,通過索引判斷得到 done ,當無資料可用時,超過陣列最大索引,無可用資料回傳,此時 done 為 true
可迭代物件
了解過了 iterator,并且我們也已經知道了如何創建一個遍歷器物件,但是這和我們先前所說的 for...of 回圈有什么關系呢
這里首先我們需要了解一下 for...of 的運行機制
當 for...of回圈執行時,回圈內部會自動呼叫這個物件上的迭代器方法Symbol.iterator , 依次執行迭代器物件的 next 方法,將 next 方法的回傳值賦值給 for ...of 內的變數,從而得到具體的值,實作遍歷,
1. 手寫實作可迭代物件
一個資料結構只要具有 Symbol.iterator 屬性,就可以認為是“可遍歷的”,
Symbol.iterator 屬性本身是一個函式,就是當前資料結構默認的遍歷器生成函式,執行這個函式,就會回傳一個迭代器物件,
也就是說要實作可迭代物件只要在物件上部署了Symbol.iterator屬性,為它創建一個迭代器方法就可以了
let iteratorObj = {
items: [1, 2, 'ljc'],
// 部署Symbol.iterator屬性
[Symbol.iterator]: function () {
let self = this
let i = 0
return {
next: function () {
// 型別轉化為Boolean
let done = (i >= self.items.length)
// 資料確認
let value = !done ? self.items[i++] : undefined
// 資料回傳
return {
done,
value
}
}
}
}
}
for (let item of iteratorObj) {
console.log(item); // 1 2 ljc
}
顯然實作了 iterator 介面,可以被 for...of 成功遍歷
2. Iterator 原生應用場景
有些物件我們并沒有為它們部署 Iterator 介面,但是仍然可以使用 for...of 進行遍歷,這是因為在ES6中有些物件已經默認部署了這個介面,
原生具備 Iterator 介面的資料結構:
- Array
- set容器
- map容器
- String
- 函式的 arguments 物件
- NodeList 物件
Array
在陣列上成功的找到了 Symbol.iterator 方法,并能夠執行回傳迭代器物件,同時驗證了for...of回圈成功執行
let arr = [1, 2, 3]
let it = arr[Symbol.iterator]()//回傳迭代器物件
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
for (let item of arr) {
console.log(item);
}

其余幾種都一個套路,不多說
Q&A
看到這里你可能會想,為什么這么多資料結構都實作了默認部署,為什么偏偏物件沒有呢?
當然是有原因的
物件可能有各種各樣的屬性,不像陣列的值是有序的,所以對物件遍歷時根本不知道如何確定先后順序,所以需要我們手動實作
提前退出回圈
普通的 for 回圈是可以隨時中斷的,for...of 回圈作為 for 和 forEach 的升級版同樣是可以的
迭代器物件除了有 next 方法,還有兩個可選方法 return 方法和 throw 方法
return 方法的使用場景是,當 for...of 回圈提前退出,就會呼叫 return 方法,
需要特別注意的是,return 方法必須有一個 object 型別的回傳值
我們在前面代碼的基礎上添加上 return 方法,并在 for...of 回圈中采用 break 陳述句來中斷回圈,回圈提前退出,自動呼叫 return 方法輸出提前退出
let iteratorObj = {
items: [1, 2, 'ljc'],
// 部署Symbol.iterator屬性
[Symbol.iterator]: function () {
let self = this
let i = 0
return {
next: function () {
// 型別轉化為Boolean
let done = (i >= self.items.length)
// 資料確認
let value = !done ? self.items[i++] : undefined
// 資料回傳
return {
done,
value
}
},
return () {
console.log('提前退出');
return {
done: true
}
}
}
}
}
for (let item of iteratorObj) {
console.log(item); // 1
break;
}
注意
如果采用拋出例外的方式退出,會先執行 return 方法再拋出例外
關于 throw 方法會在下篇生成器文章中提到
Iterator 介面使用場景
除了 for...of 回圈會自動呼叫 iterator 介面之外,還有幾個場景也會自動呼叫
1. 解構賦值
對可迭代物件進行解構賦值時,會默認呼叫 Symbol.iterator 方法
let map = new Set().add('a').add('b');
let [x, y] = map
console.log(x, y, map) // a b Set(2) {"a", "b"}
由于解構賦值適用于可迭代物件,那么我們對自己自定義的可迭代物件解構賦值試試
let iteratorObj = {
items: [1, 2, 'ljc'],
// 部署Symbol.iterator屬性
[Symbol.iterator]: function () {
let self = this
let i = 0
return {
next: function () {
// 型別轉化為Boolean
let done = (i >= self.items.length)
// 資料確認
let value = !done ? self.items[i++] : undefined
// 資料回傳
return {
done,
value
}
}
}
}
}
let [a, b, c] = iteratorObj
console.log(a, b, c)// 1 2 'ljc'
成功的實作了解構賦值
2. 擴展運算子
擴展運算子也會默認呼叫Symbol.iterator方法,可以將當前資料結構轉化為陣列
// 阮老師的例子
var str = 'hello';
[...str] // ['h','e','l','l','o']
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
3. yield*
yield*后面跟的是一個可遍歷的結構,它會呼叫該結構的遍歷器介面,
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
}
這里并不是它的主戰場,下節說明
總結
在 ES6 中新增了新的資料結構,為了提供一種統一的遍歷方法,新增了 for...of 方式,而 for...of 執行的時候會自動呼叫迭代器來取值
只有實作了 Iterator 介面的物件才能采用 for...of
迭代器是一個回傳迭代器物件的方法
ES6 中很多場景都采用了 Iterator ,可以多注意一下,新的東西往往是向上的
下一篇將來講解生成器Generator
非常感謝您的閱讀,歡迎提出你的意見,有什么問題歡迎指出,謝謝!🎈
支付寶生態技術學習交流群

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/294240.html
標籤:其他
上一篇:Javascript筆記大全03
下一篇:Axios的使用
