LoginSignup
18
13

More than 3 years have passed since last update.

TypeScript: for-ofでループできるクラスを定義する

Last updated at Posted at 2019-08-29

本稿では、TypeScriptにてfor ( ... of ... )でループできるクラスの作り方を説明する。

やりたいこと

やりたいこととしては、次のような自作のクラスがあって、

class MyList {
  constructor(private elements: Array<number>) {}
}

これをnewしたオブジェクトを、

const list = new MyList([1, 2, 3])

まるでArrayのようにfor (... of ...)でループできるようにしたい。

for (const element of list) {
  // ...
}

もちろんこのままじゃコンパイルエラーになります。

JavaScriptのオブジェクトはどういう基準でループできるのか?

まず、JavaScriptがfor-ofでループできるものが何なのか、知っておく必要がある。

ビルトインのArrayMapは、「反復処理プロトコル(itetration protocol)」に準拠したオブジェクトになっているためループできる。

つまり、自前のオブジェクトでも「反復処理プロトコル」に準拠してさえいれば、ループできるというわけである。

反復処理プロトコルを自前クラスに組み込む

反復処理プロトコルを自前クラスに組み込むには、[Symbol.iterator]()というメソッドを生やせば良い。

class MyList {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator]() {}
}

このメソッドの戻り値の型はIterator<T>にならないといけない。

class MyList {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator](): Iterator<number> {}
}

this.elementsArray<number>だが、先述したとおりArray<T>も反復処理プロトコルを実装しているので、[Symbol.iterator]メソッドを持っている。

つまり、次のように、this.elements[Symbol.iterator]()の戻り値をそのままreturnしてしまえば、コンパイルが通るようになる。

class MyList {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator](): Iterator<number> {
    return this.elements[Symbol.iterator]()
  }
}

最後に、クラス自体もIterable<number>implementsしておくといい。無くても動くが、コンパイル時にメソッドのシグネチャのチェックがなされるので、安心度が高まる。

class MyList implements Iterable<number> {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator](): Iterator<number> {
    return this.elements[Symbol.iterator]()
  }
}

このコードは試しにTypeScript playgroundで動かしてみることができる。

込み入った実装

上記では話をシンプルにするためにArray<T>[Symbol.iterator]メソッドを呼んで返すだけの実装にしたが、自前クラスに反復処理プロトコルを実装する背景には、複雑なループを隠蔽したいことがあると思う。

そこで、もう少し込み入ったイテレータの実装をサンプルとして考えてみる。

次のサンプルは、偶数だけをループする実装になる:

class MyList implements Iterable<number> {
  constructor(private elements: Array<number>) {}
  *[Symbol.iterator](): Iterator<number> {
    for (const element of this.elements) {
      if (element % 2 == 0)
        yield element
    }
  }
}

const list = new MyList([1, 2, 3, 4, 5, 6])

for (const element of list) {
  console.log(element) // 2, 4, 6のみが出力される
}

このコードはTypeScript Playgroundで動かしてみれる。

[Symbol.iterator]メソッドの頭についている*はジェネレーター関数のマークで、yieldが使えるようになる。ジェネレーターはTypeScriptでは

interface Generator extends Iterator<any> { }

と定義されており、イテレータとして扱うことができる。

18
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
13