迭代器與生成器
背景
傳統的遍歷迭代集合的方式使用for迴圈,ES6引入了迭代器物件,使得操作集合更加便捷,Set、Map、for...of、擴充套件運算子 都用到了迭代器,注:普通的物件不是可迭代的,因為沒有Symbol(Symbol.iterator)屬性
迭代器Iterator
迭代器表示可以被遍歷迭代的物件,以程式設計的方式返回集合中的下一項,迭代器物件都擁有next()方法,返回{value:'',done:bool},value表示當前迭代位置的返回值,done表示是否迭代結束,結束時其值為false
生成器Generator
是一個返回迭代器物件的函式
- 使用 function *函式名(){} 來建立生成器
- 使用生成器函式表示式建立生成器:let iterator = function *(){} 注:不能使用箭頭函式建立生成器
- 函式體中使用yield關鍵字定義每次迭代器呼叫next()返回的value結果,yield指令相當於起到暫停程式碼執行的作用,只有在迭代器呼叫next()時,才會觸發下一個yield繼續執行
- 迭代完yield後,下一次迭代會返回return的值;如果return放在中間,則迭代到return行後,後面的yield也不會執行了
function *createIterator(){
yield 1;
yield 2;
yield 3;
return 123
}
let iterator = createIterator()
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 123, done: true}
console.log(iterator.next()) //{value: undefined, done: true}
複製程式碼
可迭代物件與for...of
可迭代物件(iterable)是指帶Symbol(Symbol.iterator)屬性的物件,Symbol(Symbol.iterator) 本質上是一個生成器函式,所有的集合物件(陣列、Set、Map)及字串的原型上都預設攜帶這個屬性,因此都是可迭代物件
可迭代物件可以使用ES6新增的for-of方法遍歷
for-of的優勢:使用傳統for迴圈時,需要通過邏輯控制遍歷條件,特別是多級巢狀時會導致複雜度增加,容易出錯 for-of迴圈的原理:
- 首先內部會自動呼叫集合的Symbol.iterator方法,獲取到預設的迭代器物件
- 然後在每次迴圈時,都會自動調迭代器的next(),將返回結果物件的value值賦給臨時變數上(迭代器返回的結果物件的value就是可迭代物件上定義的值)
- 下一輪迴圈繼續呼叫next(),直到遇到返回的結果物件的done屬性值為true,停止遍歷
let arr = [1,2,3] let iterator = arr[Symbol.iterator]() console.log(iterator.next()) //{value: 1, done: false} console.log(iterator.next()) //{value: 2, done: false} console.log(iterator.next()) //{value: 3, done: false} 複製程式碼
建立可迭代物件
因為我們正常建立的物件是沒有Symbol.iterator屬性的,所以是不可迭代的。我們可以自定義一個Symbol.iterator屬性
let obj = {
items:[],
*[Symbol.iterator](){ //使用Symbol型別要使用[]
for(let item of this.items){
yield item
}
}
}
obj.items.push(1)
obj.items.push(2)
obj.items.push(3)
console.log(obj) // {items: Array(3), Symbol(Symbol.iterator): ƒ}
for(let value of obj){
console.log(value) //1,2,3
}
複製程式碼
集合內建的迭代器
ES6具有內建迭代器的集合物件型別:陣列、Set、Map
- entries():返回一個包含鍵值對的迭代器,迭代返回的結果物件value是鍵值對陣列
- values():返回一個包含集合中的值的迭代器
- keys():返回一個包含集合中的鍵的迭代器
在for-of中使用上面三種的其中一種都可以,下面是顯式指定迭代器的寫法:
let colors = ['red', 'green', 'blue']
let tracking = new Set([123,456,789])
let data = new Map()
data.set('title','understanding ES6')
data.set('format', 'ebook')
for(let entry of colors.entries()){
console.log(entry)
}
for(let entry of tracking.entries()){
console.log(entry)
}
for(let entry of data.entries()){
console.log(entry)
}
/*
[0, "red"]
[1, "green"]
[2, "blue"]
[123, 123]
[456, 456]
[789, 789]
["title", "understanding ES6"]
["format", "ebook"]
*/
for(let value of colors.values()){
console.log(value)
}
for(let value of tracking.values()){
console.log(value)
}
for(let value of data.values()){
console.log(value)
}
/*
red
green
blue
123
456
789
understanding ES6
ebook
*/
for(let key of colors.keys()){
console.log(key)
}
for(let key of tracking.keys()){
console.log(key)
}
for(let key of data.keys()){
console.log(key)
}
/*
0
1
2
123
456
789
title
format
*/
複製程式碼
三種幾個型別預設的迭代器是什麼呢?
當都有for-of迴圈沒有顯式指定迭代器時,每種集合型別都有一個預設的迭代器供迴圈呼叫
- values():陣列與Set的預設迭代器
- entries():Map的預設迭代器
let colors = ['red', 'green', 'blue']
let tracking = new Set([123,456,789])
let data = new Map()
data.set('title','understanding ES6')
data.set('format', 'ebook')
//與使用colors.values()相同
for(let value of colors){
console.log(value)
}
/*
red
green
blue
*/
//與使用tracking.values()相同
for(let value of tracking){
console.log(value)
}
/*
123
456
789
*/
//與使用data.entries()相同
//使用了陣列解構,方便操作迭代器返回的結果物件[0, "red"]格式
for(let [key,value] of data){
console.log(key + ' ' + value)
}
/*
title understanding ES6
format ebook
*/
複製程式碼
NodeList的迭代器
使用document.getElementsByXXX獲取的文件物件模型(DOM)是一種NodeList型別,其比較大的特點是,有類似陣列的length屬性,同時具有預設迭代器,因此NodeList可以使用for-of迭代
擴充套件運算子
擴充套件運算子可以應用到任何可迭代的集合上,擴充套件運算子會呼叫預設的迭代器,返回相應的結果物件value值
生成器委託:可以將多個生成器合併到一起
function *numberIterator(){
yield 1;
yield 2;
}
function *colorIterator(){
yield 'red';
yield 'blue';
}
function *combinedInterator(){
yield *numberIterator();
yield *colorIterator();
yield true
}
let interator = combinedInterator()
console.log(interator.next()) //{value: 1, done: false}
console.log(interator.next()) //{value: 2, done: false}
console.log(interator.next()) //{value: "red", done: false}
console.log(interator.next()) //{value: "blue", done: false}
console.log(interator.next()) //{value: true, done: false}
console.log(interator.next()) //{value: undefined, done: true}
複製程式碼
傳遞引數給迭代器
function * createIterator(){
let first = yield 1;
console.log('first',first)
let second = yield first *3;
console.log('second',second)
yield second * 5
}
let iterator = createIterator()
console.log(iterator.next(3)) //第一個next()中的引數不起作用
console.log(iterator.next(2))
console.log(iterator.next(4))
console.log(iterator.next())
/*
{value: 1, done: false}
first 2
{value: 6, done: false}
second 4
{value: 20, done: false}
{value: undefined, done: true}
*/
複製程式碼
- 執行第一個next(),第一個next()比較特殊,傳進來的引數被忽略
- 執行完第一個'yield 1'後,程式碼被暫停
- 執行第二個next(),next()中傳遞的引數2作為'let first = yield 1'中的返回值,賦值給first變數,所以執行'yield first *3'的結果是2x3=6
- 執行第三個next(),next()中傳遞的引數4作為second的值,以此類推
使用生成器、迭代器實現非同步任務執行
執行非同步任務的傳統方法是建立一個帶有回撥的函式,可以很完美的解決數量比較少的任務;但是如果有多層巢狀回撥,或者按順序執行一系列任務時,使用生成器和迭代器可以完成非同步任務的實現,yield指令可以起到很好暫停程式碼執行的作用
- 建立一個呼叫生成器並啟動迭代器的函式
- 呼叫該函式,傳入期望執行任務的生成器
function run(taskDef){ //呼叫生成器,得到迭代器 let task = taskDef() //啟動任務 let result = task.next() // 使用遞迴 保證迭代器的呼叫 function step(){ if(!result.done){ result = task.next() step() } } //開始處理 step() } run(function*(){ console.log(1); yield; console.log(2); yield; console.log(3); }) /* 1 2 3 */ 複製程式碼