setInterval
やsetTimeout
系の処理をclass内で呼び出したときに、this
の値がおかしくなり、対応法でハマったのでメモしておきます📝
結論
setInterval
等のロジックで呼び出す関数にbind(this)
をつけて、this
を明示する
bind() メソッドは、呼び出された際に this キーワードに指定された値が設定される新しい関数を生成します。
コード例
うまく動かないコードの例
以下のようなタイマーを管理するような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) オブジェクトに設定されます。
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()