1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ひとりJavaScriptAdvent Calendar 2024

Day 18

【JavaScript】ジェネレーターって何?

Last updated at Posted at 2024-12-17

ジェネレーター関数とは

ジェネレーターを理解するためには、先にジェネレーター関数について知っておくと理解が早いと思います。

ジェネレーター関数とは

ジェネレーター関数はイテレーターを作るための関数です。
これはfunction*(最後に*がつく)キーワードによって定義できます。

function* 関数名(引数) {
  // 処理
}
イテレーターとは

イテレーターは何かを列挙するためのオブジェクトです。
nextメソッドを持ち、このメソッドが{ done: boolean, value: T }形式のオブジェクトを返す場合、それはイテレーターと呼べます。

また、Symbol.itereatorメソッドを持ち、これがイテレーターを返す場合、そのオブジェクトはイテラブルだと呼べます。
イテラブルなオブジェクトはfor...ofやスプレッド構文が使えます。

詳細はMDNをご覧ください。

また、ジェネレーター関数は後述のジェネレーターを返します。
ジェネレーターはとりあえずイテレーターのようなものだと思ってください。

yield

ジェネレーター関数において、イテレーターのnextメソッドが返す値はyieldを使うことで指定できます。
yieldは通常の関数で使うことはできません。

構文はyield 返したい値です。

function* numbers() {
  yield 1
  yield 2
  yield 3
}

例: 1~10を表現する

例えば、1~10を表現するイテレーターを作成したいと思います。
イテレーターは使い捨てにしたいので、簡単にイテレーターのオブジェクトを作成できる方法を用意する必要があります。

今回はmakeIteratorというジェネレーター関数を用意し、これを呼び出すと新しくイテレーター(ジェネレーター)が作成されるようにします。

普通に実装した場合

ジェネレーター関数を使わず普通に実装した場合は、おおよそ以下のようになると思います。

function makeIterator() {
  // クロージャで状態を管理
  let current = 0
  
  // イテレーターを返す
  return {
    next() {
      current++
      // すでに10を超えている場合
      if (current > 10) return { done: true }
      return { done: false, value: current }
    }
  }
}

const iterator = makeIterator()
iterator.next() // { done: false, value: 1 }
iterator.next() // { done: false, value: 2 }
// ...
iterator.next() // { done: false, value: 10 }
iterator.next() // { done: true }

この方法だとネストが深くなり、少し冗長な印象があります。

ジェネレーター関数を使った場合

// function*キーワードでジェネレーター関数を定義
function* makeIterator() {
  let current = 1
  while (current <= 10) {
    yield current
    current++
  }
}

const iterator = makeIterator()
iterator.next() // { done: false, value: 1 }
iterator.next() // { done: false, value: 2 }
// ...
iterator.next() // { done: false, value: 10 }
iterator.next() // { done: true, value: undefined }

ジェネレーター関数では、普通のwhileの中にyieldを入れることで、簡潔に実装できます。
ネストも減ってコードも読みやすくなったと思います。

ジェネレーターとは

ジェネレーターは、ジェネレーター関数を呼び出すと得られるオブジェクトです。

ジェネレーターは以下の性質を持っています。

上で定義したmakeIterator関数はジェネレーターを返します。
そしてジェネレーターはイテレーターなので、nextメソッドを呼び出すことができました。

イテラブルである

ジェネレーターはイテラブルなので、for...of文やスプレッド構文が直接使えます。

const iterator = makeIterator() // ジェネレーターを作成
const array = [...iterator] // スプレッド構文で配列に変換
// [1, 2, 3, ..., 9, 10]

もしジェネレーター関数を使わずに同じことをやろうとすると、少し大変です。
イテレーターは通常イテラブルではないので、[Symbol.iterator]メソッドを持ったオブジェクトでラップする必要があります。

function makeItereator() {
  // ジェネレーター関数を使わずに実装する
}

function makeIterable() {
  return {
    [Symbol.iterator]() {
      return makeIterator()
    }
  }
}

const iterable = makeIterable()
const array = [...iterable]
// [1, 2, 3, ..., 9, 10]

もしくは[Symbol.iterator]メソッドに実装を書くことになるでしょう。

作例: range

ジェネレーター関数なら、範囲を示すrangeの簡単な実装もできます。
ここでのrangeは以下の引数を取ります。

  1. start: 範囲の開始地点
  2. end: 範囲の終了地点、end自身は含まない
  3. step: 範囲内の数値をいくつづつ増やすか、デフォルトでは1

実装は先ほどとほぼ同じようにできます。
ここでは以下のようになりました。

function* createRange(start: number, end: number, step: number = 1) {
  let current = start // currentを初期化
  // currentがendを超えるまでyield
  while (current < end) {
    yield current
    current += step // stepだけcurrentを増やす
  }
}
使用例
const range = createRange(1, 11); // 1 ~ 10(11は含まない)
[...range] // [1, 2, 3, ..., 10, 11]

このrangeにはジェネレーターが入っています。
ただのジェネレーターなので、スプレッド構文に入れたり、直接for...ofループに入れたりできます。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?