[JS] JavaScript 疊代器(Iterator)
觀念
- 在 ES6 中,有三種資料結構原生就具備 Iterator 接口:
Array
、Array-like Object
、Set
和Map
結構 - 除了上述這些之外,其他資料結構(主要是物件)都需要自己在
Symbol.iterator
屬性上實作 iterator,這樣才會被for...of
迴圈遍歷。 - iterator 會返回
value
(當前成員的值) 和done
(Boolean,表示是否遍歷結束)
只要有在 Symbol.iterator
這個屬性上實作 iterator 的 interface,就表示這個資料結構是 iterable,也就是可以使用 for...of
來遍歷:
/**
* Iterator 可以透過 Symbol.iterator 來客制化疊代的方式
* 裡面會包含 next function,這個 function 會回傳 value 和 done
* 像這樣:
**/
type IteratorFn = () => {
next(): { value: any; done: boolean };
};
type Iterator = {
[Symbol.iterator]: IteratorFn;
};
基本使用
在 Symbol 時有提到 JS 中有內建的 Symbol,大部分是提供內建的函式方法(Well-known Symbols),其中 Symbol.iterator
就是裡面內的 Symbol,對於可疊代的型別,在 Symbol.iterator 中就有提供函式可以讓它在 for...of
時使用。
/**
* Demonstrate an iterator
**/
let arr = [1, 2, 3];
console.log(typeof arr[Symbol.iterator]); // function
let current = arr[Symbol.iterator]();
current.next(); // Object {value: 1, done: false}
current.next(); // Object {value: 2, done: false}
current.next(); // Object {value: 3, done: false}
current.next(); // Object {value: undefined, done: true}
模擬 iterator 的 function
/**
* Create a function to simulate iterator
**/
var it = makeIterator(['a', 'b']);
it.next(); // { value: "a", done: false }
it.next(); // { value: "b", done: false }
it.next(); // { value: undefined, done: true }
function makeIterator(array) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < array.length
? { value: array[nextIndex++], done: false }
: { value: undefined, done: true };
},
};
}
搭配 for...of,客制化自己的 iterator
只要有在 Symbol.iterator
實作 iterator 的 interface,它就是 iterable,即可被 for...of
所迭代。
for...of
在當 Iterator 的 done
為 true
時就不再疊代。
Array 部分
/**
* Create a custom iterable array
**/
arr[Symbol.iterator] = function () {
let nextIndex = 0;
return {
next() {
return nextIndex < arr.length
? {
value: arr[nextIndex++] * 2,
done: false,
}
: {
value: undefined,
done: true,
};
},
};
};
for (item of arr) {
// 當 done 為 true 時就不在疊代
console.log(item); // 2, 4, 6
}
物件部分
/**
* Create a custom iterable Object
**/
let person = {
firstName: 'Aaron',
lastName: 'Chen',
hobbies: ['computer', 'programming', 'sports'],
// 讓 JS 知道這個物件具有 iterator,所以是 iterable
[Symbol.iterator]: function () {
let index = 0;
let hobbies = this.hobbies;
return {
next() {
return index < hobbies.length
? {
done: false,
value: hobbies[index++],
}
: {
done: true,
value: undefined,
};
},
};
},
};
for (let hobby of person) {
console.log(hobby); // computer, programming, spots
}
也可以使用 Class:
type IteratorFn = () => {
next(): { value: any; done: boolean };
};
type Iterator = {
[Symbol.iterator]: IteratorFn;
};
class Countdown implements Iterator {
#start: number;
constructor(start: number) {
this.#start = start;
}
[Symbol.iterator] = function () {
let current = this.#start;
return {
next() {
if (current >= 0) {
return {
value: current--,
done: false,
};
} else {
return {
value: undefined,
done: true,
};
}
},
};
};
}
// 因為 Countdown 有實作 Iterator,所以可以用 for...of 加以迭代
for (let i of new Countdown(3)) {
console.log(i); // 3 2 1 0
}
Demo Code
ES6 Iterator @ PJCHENder JSFiddle