Iterator
イテレータ自体はTypeScriptまたはES6の機能ではなく、オブジェクト指向プログラミング言語において一般的な、"振る舞いに関するデザインパターン"です。これは、一般的に次のインターフェースを実装するオブジェクトです。
1
interface Iterator<T> {
2
next(value?: any): IteratorResult<T>;
3
return?(value?: any): IteratorResult<T>;
4
throw?(e?: any): IteratorResult<T>;
5
}
Copied!
(<T>記法についてはのちに説明します) このインターフェースは、コレクションまたはシーケンスのオブジェクトに属する値を取得することを可能にします。
IteratorResultは単なるvalue+doneのペアです:
1
interface IteratorResult<T> {
2
done: boolean;
3
value: T;
4
}
Copied!
何らかのframeという名前のオブジェクトがあると想像してください。このframeは、コンポーネントの一覧で構成されています。イテレータのインターフェースは、frameオブジェクトのコンポーネントを次のように取得することを可能にします。
1
class Component {
2
constructor (public name: string) {}
3
}
4
5
class Frame implements Iterator<Component> {
6
7
private pointer = 0;
8
9
constructor(public name: string, public components: Component[]) {}
10
11
public next(): IteratorResult<Component> {
12
if (this.pointer < this.components.length) {
13
return {
14
done: false,
15
value: this.components[this.pointer++]
16
}
17
} else {
18
return {
19
done: true
20
}
21
}
22
}
23
24
}
25
26
let frame = new Frame("Door", [new Component("top"), new Component("bottom"), new Component("left"), new Component("right")]);
27
let iteratorResult1 = frame.next(); //{ done: false, value: Component { name: 'top' } }
28
let iteratorResult2 = frame.next(); //{ done: false, value: Component { name: 'bottom' } }
29
let iteratorResult3 = frame.next(); //{ done: false, value: Component { name: 'left' } }
30
let iteratorResult4 = frame.next(); //{ done: false, value: Component { name: 'right' } }
31
let iteratorResult5 = frame.next(); //{ done: true }
32
33
// `value`プロパティを経由して、イテレータの戻り値を取得することができます
34
let component = iteratorResult1.value; // Component { name: 'top' }
Copied!
繰り返しになりますが、イテレータ自体はTypeScriptの機能ではありません。このコードはIteratorIteratorResultのインターフェースを明示的に実装しなくても動作します。しかしながら、ES6のインターフェースを使うことはコードの一貫性を保つ上で非常に便利です。
上のコードでも良いでしょう。しかし、もっと便利にできます。ES6は反復可能プロトコルを定義していおり、その1つは[Symbol.iterator]シンボルです。これを利用して、for...ofで反復可能なオブジェクトを実装できます:
1
//...
2
class Frame implements Iterable<Component> {
3
4
constructor(public name: string, public components: Component[]) {}
5
6
[Symbol.iterator]() {
7
let pointer = 0;
8
let components = this.components;
9
10
return {
11
next(): IteratorResult<Component> {
12
if (pointer < components.length) {
13
return {
14
done: false,
15
value: components[pointer++]
16
}
17
} else {
18
return {
19
done: true,
20
value: null
21
}
22
}
23
}
24
}
25
}
26
}
27
28
let frame = new Frame("Door", [new Component("top"), new Component("bottom"), new Component("left"), new Component("right")]);
29
for (let cmp of frame) {
30
console.log(cmp);
31
}
Copied!
残念ながら frame.next()はこのパターンでは動作しません。また、見た目が少し不格好です。そこで助けになるのがTypeScriptで利用できる IterableIterator インターフェースです:
1
//...
2
class Frame implements IterableIterator<Component> {
3
4
private pointer = 0;
5
6
constructor(public name: string, public components: Component[]) {}
7
8
public next(): IteratorResult<Component> {
9
if (this.pointer < this.components.length) {
10
return {
11
done: false,
12
value: this.components[this.pointer++]
13
}
14
} else {
15
return {
16
done: true,
17
value: null
18
}
19
}
20
}
21
22
[Symbol.iterator](): IterableIterator<Component> {
23
return this;
24
}
25
26
}
27
//...
Copied!
frame.next()forループの両方が、IterableIteratorインターフェースでうまく動作するようになりました。
イテレータが反復する対象は有限個である必要はありません。典型的な例はフィボナッチ計算の処理です:
1
class Fib implements IterableIterator<number> {
2
3
protected fn1 = 0;
4
protected fn2 = 1;
5
6
constructor(protected maxValue?: number) {}
7
8
public next(): IteratorResult<number> {
9
var current = this.fn1;
10
this.fn1 = this.fn2;
11
this.fn2 = current + this.fn1;
12
if (this.maxValue != null && current >= this.maxValue) {
13
return {
14
done: true,
15
value: null
16
}
17
}
18
return {
19
done: false,
20
value: current
21
}
22
}
23
24
[Symbol.iterator](): IterableIterator<number> {
25
return this;
26
}
27
28
}
29
30
let fib = new Fib();
31
32
fib.next() //{ done: false, value: 0 }
33
fib.next() //{ done: false, value: 1 }
34
fib.next() //{ done: false, value: 1 }
35
fib.next() //{ done: false, value: 2 }
36
fib.next() //{ done: false, value: 3 }
37
fib.next() //{ done: false, value: 5 }
38
39
let fibMax50 = new Fib(50);
40
console.log(Array.from(fibMax50)); // [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
41
42
let fibMax21 = new Fib(21);
43
for(let num of fibMax21) {
44
console.log(num); //Prints fibonacci sequence 0 to 21
45
}
Copied!

ES5で動作するイテレータを使ってコードを書く

上記のコード例はES6をターゲットにしてコンパイルする必要がありますが、ES5をターゲットにしても、 Symbol.iterator をサポートしている場合は、動作する可能性があります。これは、ES6 lib(es6.d.ts)をプロジェクトに追加してES5をターゲットにコンパイルすることで可能です。コンパイルされたコードは、node 4+、Google Chrome、その他のブラウザで動作するはずです。
最終更新 1yr ago