JavaScriptにおけるタイマーとコンテキストの喪失
イベントハンドラー内でタイマーを使用する際には、 コンテキスト喪失の問題が待ち構えています。 例を見てみましょう。
次のような入力フィールドがあるとします:
<input id="elem" value="text">
この入力フィールドをクリックした際に匿名関数が実行され、 その関数内でタイマーが起動し、毎秒何かをコンソールに 出力するようにしましょう:
let elem = document.querySelector('#elem');
elem.addEventListener('click', function() {
setInterval(function() {
console.log('!!!'); // コンソールに何かを出力
}, 1000);
});
ここまでは正常に動作します。しかし、今度はコンソールに
入力フィールドの value を出力したいとすると、
驚きが待っています:コンソールには undefined
が出力されます:
elem.addEventListener('click', function() {
setInterval(function() {
console.log(this.value); // undefinedが出力される
}, 1000);
});
問題は、関数内関数ができてしまうことです。
クリック時に呼び出される外側の匿名関数と、
タイマーによって起動される内側の匿名関数があります。
外側の関数では this は入力フィールドを指しますが、
内側の関数ではそうではありません。コンテキストの喪失が
発生しています。
なぜ、以前のレッスンで起こったようにコンソールエラーに
ならないで undefined が出力されるのでしょうか?
それは、setInterval 経由で呼び出される関数内の
this が window を指すためです。
つまり、window オブジェクトから value プロパティを
読み取ろうとしているのです。このように:window.value。
window オブジェクトにはそのようなプロパティは存在しないため、
(エラーではなく)undefined を得るのです。
self を導入してこの問題を修正しましょう:
elem.addEventListener('click', function() {
let self = this;
setInterval(function() {
console.log(self.value);
}, 1000);
});
次のようなコードがあるとします:
<input type="button" id="elem" value="1">
let elem = document.querySelector('#elem');
elem.addEventListener('click', function() {
setInterval(function() {
this.value = Number(elem.value) + 1;
}, 1000);
});
このコードの作者は、ボタンが押されると、
そのボタンの値が毎秒 1 ずつ増加することを
期待していました。しかし、ボタンを押しても
何も起こりません。作者のコードの誤りを修正し、
なぜそのエラーが発生したのかを作者に説明する
文章を書いてください。