setTimeout()やsetInterval()で関数に引数を渡す

やりたいこと

JavaScriptで setTimeout() や setInterval() のタイマハンドラに引数を渡したい。

ダメな書き方

これはダメです。実行するとエラーが発生します。setTimeout() の第1引数に関数ではなく関数の戻り値を渡すことになるからです。しかも、hello()には戻り値がないので、undefined が渡されてしまいます。

function hello(text) {
  console.log(text);
};

const timer = setTimeout(hello('Hello, world!'), 1000);

正しい書き方(1):第3引数以降にハンドラの引数を渡す

こう書けば簡潔ですし、正しく動作しますが、やや分かりにくいです。

function hello(text) {
  console.log(text);
};

const timer = setTimeout(hello, 1000, 'Hello, world!');

正しい書き方(2):第1引数にハンドラを呼ぶ無名関数を渡す

少しだけ長くなりますが、こう書いた方が分かりやすいです。

function hello(text) {
  console.log(text);
};

const timer = setTimeout( function(){ hello('Hello, world!') }, 1000);

正しい書き方(3):bind(引数束縛)を使う

いちおう、こういう書き方もあります。JavaScriptの関数オブジェクトは引数束縛のためのメソッド bind() を持ちます。bind() の第1引数は this を束縛し、第2引数以降が元の関数の引数を束縛します。この例では this が不要なので undefined を渡しています。ですが、すこし分かりにくいですね。

function hello(text) {
  console.log(text);
};

const timer = setTimeout(hello.bind(undefined, 'Hello, world!'), 1000);

【追記】いや、(1) と (2) は違うぞ!

よく考えたら、(1)、(3) と (2) は挙動が違います。(1) と (3) は setTimeout() の実行時に引数の値が確定しますが、(2) はハンドラである無名関数が実行時に hello() に渡す引数の値が評価されます。したがって、引数が変数の場合は下記のように結果が変わることがあります。

function hello(text) {
  console.log(text);
};

let text = 'Hello, world!';

// (1) => Hello, world!
// const timer = setTimeout(hello, 1000, text);

// (2) => Goodbye, world!
const timer = setTimeout(function() { hello(text) }, 1000);

// (3) => Hello, world!
// const timer = setTimeout(hello.bind(undefined, text), 1000);

text = 'Goodbye, world!';

参考