非同期のコールバック関数に外から値を渡す
コールバック関数のパラメータ以外のものを外から渡す方法について。
ここでは setTimeout() の第1引数に渡すコールバック関数を例にします。
単に主題の件をやりたければ、コールバック関数のスコープ外で宣言した変数に渡したい値をセットしておいて、それをコールバック関数の内側から参照させればいいです。
例として次のコードを実行すると、1000ミリ秒後に alert で『(^-^)』が表示されます。この場合、str がコールバック関数のスコープ外の変数にあたります。
const str = '(^-^)';
setTimeout(() => {
alert(str);
}, 1000);ただしこの方法で参照される str の値は、実際にコールバック関数が実行された時点における str の値となります。
それを確認できるのが次の例です。次のコードを実行すると、1000ミリ秒後に alert() で表示されるのは『(^-^)』ではなく『(>_<)』です。
let str = '(^-^)';
setTimeout(() => {
alert(str); // 実際にこれが実行される時点での str は '(>_<)'
}, 1000);
str = '(>_<)';setTimeout() はnミリ秒後に行う処理を予約する関数です。setTimeout() で alert() を予約した後、1000ミリ秒後までに str の中身が変化してるので、このようになります。
もし「コールバック関数を仕掛けたタイミングにおける変数の値」を実行時にも使用したい場合は、次のように書きます。次のコードを実行すると、1000ミリ秒後も alert で『(^-^)』が表示されます。
let str = '(^-^)';
setTimeout(createCallback(str), 1000);
str = '(>_<)';
function createCallback(arg) {
return () => {
alert(arg);
}
}これは、「『alert() を行う無名関数』を生成して返却する別の関数 (ここでは createCallback())」を間に挟んでる形です。
JavaScriptの引数は、Object型ではない限り値渡し = 『値がコピーされた別の変数』扱いです。createCallback() で生成した無名関数が参照している arg は、あくまで createCallback() が実行されたときに渡された str のコピーなので、こういうことができます。
余談
なんでこんな記事を書いたのかというと、初心者の頃にループ内で非同期のコールバック関数に外からループカウンタを渡して、想定通りに動かなかったことがあるからです。
例として次のコードを実行すると、1000ミリ秒後に alert() で 3 が3回表示されます。実際に setTimeout() で仕掛けたコールバックが実行されるタイミングでは、すでに i の値がループを走り抜けて 3 に変化しているためです。
// 変数宣言が var なのは当時の再現です
for (var i = 0; i < 3; ++i) {
setTimeout(() => {
alert(i); // 実行時には i = 3
}, 1000);
}なお、このケースに限っては var の代わりに let を使うだけで解決します。let ならばループごとに別スコープ扱いになるので、同じように書いても1000ミリ秒後に alert() で 0 -> 1 -> 2 と表示されます。
for (let i = 0; i < 3; ++i) {
setTimeout(() => {
alert(i); // 実行時には i = 0 -> 1 -> 2
}, 1000);
}
キリウ君が読まないノート