如何使用 TypeScript 的 Generator 實踐 Iterator Pattern ?
在 C# 有著名的 IEnumerable 與 IEnumerator interface,只要 implement 這兩個 interface,就可以實踐 Iterator Pattern,但 TypeScript 該如何實做呢 ?
Version
TypeScript 2.6
User Story
1 | const school = new School<Student>(); |
School 內包含很多 Student,我們希望直接使用 for of 對 School 抓到所有 Student。
for of為 ES6 的新語法,可以想像成是 C# 的foreach()。
Task
yield 本身回傳就是 iterator,因此若使用 yield 將更精簡。
Architecture
Schoool implement 了 Iterable<T> interface,且必須回傳 Iterator<T> interface。
Implementation
Iterable1
2
3interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
定義 Iterator 該有的 method
一個 iterator 一定要有 [Symbol.iterator](),並且回傳 Iterator<T>,其中 yield 可直接回傳 Iterator<T>。
Iterator1
2
3
4
5interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
定義
[Symbol.iterator]()回傳結果的 interface
不用擔心要實作這個 interface 很困難,yield 回傳的正是 Iterator<T>,只要簡單使用 yield 即可。
Student.ts1
2
3export interface Student {
Name: string;
}
定義資料類型為 Student interface,只有 Name public field。
School.ts1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { Student } from './student';
export class School<T extends Student> implements Iterable<T> {
private students: T[];
constructor() {
this.students = [
<T>{ Name: 'Sam' },
<T>{ Name: 'Kevin' },
<T>{ Name: 'Jessie' }
];
}
*[Symbol.iterator]() {
for (const iter of this.students)
{
yield iter;
}
}
}
第 3 行
1 | export class School<T extends Student> implements Iterable<T> { |
為了 School 實踐 iterator,使其 implement IterableIterator<T>,但這個 T 並不是任意的 T,被限定只能繼承於 Student interface。
第 4 行
1 | private students: T[]; |
School 的內部資料 Student[],其中 Student 以 T 取代。
第 6 行
1 | constructor() { |
在 constructor 將目前的 students 資料建立起來。
14 行
1 | *[Symbol.iterator]() { |
Symbol.iteratorproperty 回傳一個Iterator<T>物件供使用端操作
根據 Iterable<T> 規定,一定要提供 Symbol.iterator property,且回傳型別 Iterator<T>,而 yield 回傳的正是 Iterator<T>。
yield 與 return 不同的是 : 執行到 yield 會立即中止,當一個 for...of 執行時,會繼續下一個 yield,因此不像 return 是回傳一個陣列,然後使用端又不須再一次 for..of。
使用
yield前後只有一次for...of。
另外 ES6 規定,只要回傳的是 iterator,則 function 之前要加 *。
除此之外,ES6 提供更精簡的寫法
1 | *[Symbol.iterator]() { |
只要 yield*,後面加上 array,編譯展開相當於
1 | for (const iter of this.students) |
Conclusion
- Iterator Pattern 讓我們不用暴露 object 內部資料,卻又可以讓使用端以
for of方式訪問內部資料。 - Iterator Pattern 在實務上可以讓我們避免回傳一個大陣列影響效能,大陣列需要浪費時間建立,也浪費記憶體。
- 若使用 generator,則程式碼更短,意圖也更明顯,還可以使用更精簡的
yield*寫法。