Madogiwa Blog

主に技術系の学習メモに使っていきます。

JavaScript: class内でsetIntervalしたときにthisをclassのinstanceにするMEMO

setIntervalsetTimeout系の処理をclass内で呼び出したときに、thisの値がおかしくなり、対応法でハマったのでメモしておきます📝

結論

setInterval等のロジックで呼び出す関数にbind(this)をつけて、thisを明示する

bind() メソッドは、呼び出された際に this キーワードに指定された値が設定される新しい関数を生成します。

developer.mozilla.org

コード例

うまく動かないコードの例

以下のようなタイマーを管理するようなclassがあるとします。 start()実行時にsetIntervalで内部で保持する値を1秒おきにカウントアップします。

class Timer {
  constructor() {
    this.value = 0
    this.timerId = 0
  }

  countUp() {
    this.value += 1
    console.log("value", this.value)
  }

  start() {
    this.timerId = setInterval(this.countUp, 1000)
  }

  stop() {
    clearInterval(this.timerId)
  }
}
let timer = new Timer();
timer.start()

しかしこのコードはうまく動かず結果はvalue NaNが毎秒表示されるような結果になってします。

これはsetIntervalの実行コンテキストが以下の通り、window (または global) オブジェクトに設定され、インスタンスの値が読み出せなくなってしまうからです。。。

setInterval() によって実行されるコードは、setInterval が呼び出された関数とは別の実行コンテキスト内で実行されます。その結果、呼び出された関数の this キーワードは window (または global) オブジェクトに設定されます。

WindowOrWorkerGlobalScope.setInterval() - Web API | MDN

bindを使ってthisを明示する

以下のようにsetInterval(this.countUp.bind(this), 1000)とするとbindによって、thisが明示的にclassのインスタンスとなるため、想定通りの挙動になります 👍

class Timer {
  constructor() {
    this.value = 0
    this.timerId = 0
  }

  countUp() {
    this.value += 1
    console.log("value", this.value)
  }

  start() {
    // bindでthisを明示的にclassのインスタンスになるようにする
    this.timerId = setInterval(this.countUp.bind(this), 1000)
  }

  stop() {
    clearInterval(this.timerId)
  }
}

let timer = new Timer();

// `value 1`とちゃんと取れる
timer.start()

参考

foreignkey.toyao.net