タイマー

JavaScript はタイマーと呼ばれるコードを定期的に実行する機能を提供します。これは主に setTimeout()setInterval() の 2 つの関数によって実現されます。スケジュールされたタスクをタスク キューに追加します。

setTimeout()

setTimeout 関数は、特定の関数または特定のコード部分が実行されてから何ミリ秒後に実行されるかを指定するために使用されます。タイマー番号を表す整数を返します。これは後でタイマーをキャンセルするために使用できます。

var timerId = setTimeout(関数|コード, 遅延);

上記のコードでは、setTimeout 関数は 2 つのパラメーターを受け取ります。最初のパラメーター func|code は遅延する関数の名前またはコードの一部であり、2 番目のパラメーター delay は遅延するミリ秒数です。実行を遅らせます。

コンソール.ログ(1);
setTimeout('console.log(2)',1000);
コンソール.ログ(3);
// 1
// 3
// 2

上記のコードは、最初に 1 と 3 を出力し、次に 2 を出力する前に 1000 ミリ秒待機します。 console.log(2) は、setTimeout のパラメータとして文字列形式でなければならないことに注意してください。

延期された実行が関数の場合は、関数名を setTimeout のパラメータとして直接使用します。

関数 f() {
  コンソール.ログ(2);
}

setTimeout(f, 1000);

setTimeout の 2 番目のパラメータを省略した場合、デフォルトは 0 になります。

setTimeout(f)
// と同等
setTimeout(f, 0)

最初の 2 つのパラメータに加えて、setTimeout ではさらに多くのパラメータも使用できます。これらは、遅延関数 (コールバック関数) に順番に渡されます。

setTimeout(関数 (a,b) {
  console.log(a + b);
}、100011);

上記のコードでは、「setTimeout」には合計 4 つのパラメータがあります。最後の 2 つのパラメーターは、コールバック関数が 1000 ミリ秒後に実行されるときに、コールバック関数のパラメーターとして使用されます。

もう 1 つ注意すべきことは、コールバック関数がオブジェクトのメソッドである場合、setTimeout はメソッド内の this キーワードを、それが定義されたオブジェクトではなくグローバル環境を指すようにすることです。

var x = 1;

var obj = {
  ×:2y: 関数 () {
    console.log(this.x);
  }
};

setTimeout(obj.y, 1000) // 1

上記のコードは 2 ではなく 1 を出力します。なぜなら、1000 ミリ秒後に「obj.y」が実行されると、「this」はもはや「obj」ではなく、グローバル環境を指すからです。

この問題を防ぐための 1 つの回避策は、「obj.y」を関数に入れることです。

var x = 1;

var obj = {
  ×:2y: 関数 () {
    console.log(this.x);
  }
};

setTimeout(関数() {
  obj.y();
}, 1000);
// 2

上記のコードでは、obj.y を匿名関数に配置しているため、グローバル スコープではなく obj のスコープ内で obj.y が実行されるため、正しい値が表示されます。

別の解決策は、「bind」メソッドを使用して「obj.y」メソッドを「obj」にバインドすることです。

var x = 1;

var obj = {
  ×:2y: 関数 () {
    console.log(this.x);
  }
};

setTimeout(obj.y.bind(obj), 1000)
// 2

setInterval()

setInterval 関数の使用法は setTimeout とまったく同じです。唯一の違いは、setInterval がタスクを一定の間隔で実行することを指定することです。つまり、スケジュールされた実行回数は無制限です。

変数 i = 1
var timer = setInterval(function() {
  コンソール.ログ(2);
}、1000)

上記のコードでは、2 が 1000 ミリ秒ごとに出力され、現在のウィンドウが閉じるまで無期限に実行されます。

setTimeout と同様に、最初の 2 つのパラメータに加えて、setInterval メソッドはコールバック関数に渡されるさらに多くのパラメータを受け入れることもできます。

以下は、setInterval メソッドを使用して Web ページ アニメーションを実装する例です。

var div = document.getElementById('someDiv');
var 不透明度 = 1;
var fader = setInterval(function() {
不透明度 -= 0.1;
if (不透明度 >= 0) {
div.style.opacity = 不透明度;
} それ以外 {
クリアインターバル(フェーダー);
}
}, 100);

上記のコードは、「div」要素の透明度を完全に透明になるまで 100 ミリ秒ごとに設定します。

setInterval の一般的な使用法は、ポーリングを実装することです。以下は、URL のハッシュ値が変更されたかどうかをポーリングする例です。

var ハッシュ = ウィンドウ.場所.ハッシュ;
var hashWatcher = setInterval(function() {
if (window.location.hash != ハッシュ) {
UpdatePage();
}
}, 1000);

setInterval は「実行開始」の間隔を指定するもので、各タスクの実行自体にかかる時間は考慮されません。したがって、実際には、実行間隔は指定された時間より短くなります。たとえば、setInterval は 100 ミリ秒ごとの実行を指定し、各実行には 5 ミリ秒かかります。その後、最初の実行が終了してから 95 ミリ秒後に 2 回目の実行が開始されます。実行に特に長い時間がかかる場合 (たとえば 105 ミリ秒)、終了後すぐに次の実行が開始されます。

2 つの実行間の間隔を一定にするために、代わりに setInterval を使用して、各実行後の次の実行の特定の時間を指定できます。

変数 i = 1;
var timer = setTimeout(function f() {
  // ...
  タイマー = setTimeout(f, 2000);
}、2000);

上記のコードにより、次の実行が常にこの実行の終了後 2000 ミリ秒後に開始されることが保証されます。

clearTimeout()、clearInterval()

setTimeout 関数と setInterval 関数は両方とも、カウンタ番号を表す整数値を返します。整数を「clearTimeout」関数と「clearInterval」関数に渡して、対応するタイマーをキャンセルします。

var id1 = setTimeout(f, 1000);
var id2 = setInterval(f, 1000);

クリアタイムアウト(id1);
クリアインターバル(id2);

上記のコードでは、両方のタイマーがキャンセルされたため、コールバック関数 f は実行されなくなります。

setTimeoutsetInterval によって返される整数値は連続しています。つまり、2 番目の setTimeout メソッドによって返される整数値は、最初の整数値より 1 大きくなります。

関数 f() {}
setTimeout(f, 1000) // 10
setTimeout(f, 1000) // 11
setTimeout(f, 1000) // 12

上記のコードでは、setTimeout が連続して 3 回呼び出され、戻り値は前回より 1 大きくなります。

これを利用して、現在のすべての setTimeout タイマーをキャンセルする関数を作成できます。

(関数() {
  // イベントループのラウンドごとに 1 回チェックします
  var gid = setInterval(clearAllTimeouts, 0);

  関数clearAllTimeouts() {
    var id = setTimeout(function() {}, 0);
    while (id > 0) {
      if (id !== gid) {
        クリアタイムアウト(id);
      }
      id--;
    }
  }
})();

上記のコードでは、最初に setTimeout を呼び出して計算機の番号を取得し、それより小さい番号を持つすべてのカウンターをキャンセルします。

例: デバウンス関数

場合によっては、コールバック関数を頻繁に呼び出したくない場合があります。たとえば、ユーザーが Web ページの入力ボックスの内容を入力し、それを Ajax メソッドを通じてサーバーに送り返すことを希望する場合、jQuery は次のように記述されます。

$('textarea').on('keydown', ajaxAction);

この方法で記述することには大きな欠点があります。つまり、ユーザーがキーを押し続けると、keydown イベントが継続的にトリガーされ、大量の Ajax 通信が発生することになります。これは不要であり、パフォーマンスの問題が発生する可能性があります。正しいアプローチは、2 つの Ajax 通信間の最小間隔を表すしきい値を設定することです。間隔内に新しい「keydown」イベントが発生した場合、Ajax 通信はトリガーされず、計測が再開されます。指定された時間が経過しても新しい keydown イベントが発生しない場合、データは再度送信されます。

この方法はデバウンスと呼ばれます。 2 つの Ajax 通信の間隔が 2500 ミリ秒以上でなければならないと仮定すると、上記のコードは次のように書き換えることができます。

$('textarea').on('keydown', debounce(ajaxAction, 2500));

関数 debounce(fn, 遅延){
  var timer = null // タイマーを宣言します。
  戻り関数() {
    var context = this;
    var args = 引数;
    クリアタイムアウト(タイマー);
    タイマー = setTimeout(function () {
      fn.apply(コンテキスト, 引数);
    }、 遅れ);
  };
}

上記のコードでは、ユーザーが 2500 ミリ秒以内にキーを再度押すと、最後のタイマーがキャンセルされ、新しいタイマーが作成されます。これにより、コールバック関数間の呼び出し間隔が少なくとも 2500 ミリ秒になることが保証されます。

動作機構

setTimeoutsetInterval の操作メカニズムは、指定されたコードを現在のイベント ループの外に移動し、次のイベント ループまで待機し、指定された時間が経過したかどうかを確認することです。到着した場合は、対応するコードを実行します。そうでない場合は、待機を続けます。

これは、setTimeout および setInterval で指定されたコールバック関数は、実行を開始する前に、このラウンドのイベント ループのすべての同期タスクが完了するまで待機する必要があることを意味します。前のタスクが完了するまでにどれくらいの時間がかかるかは不確実であるため、setTimeout および setInterval で指定されたタスクがスケジュールされた時間に従って実行されることを保証する方法はありません。

setTimeout(someTask, 100);
非常に長いタスク();

上記のコードの setTimeout は、100 ミリ秒後にタスクを実行するように指定しています。ただし、後続の veryLongTask 関数 (同期タスク) が非常に長時間実行され、100 ミリ秒後に終了できない場合、遅延した someTask は、実装の番になる前に veryLongTask の実行が終了するまで待機する必要があります。

setInterval の別の例を見てみましょう。

setInterval(関数() {
  コンソール.ログ(2);
}, 1000);

スリープ(3000);

関数スリープ(ミリ秒) {
  var start = Date.now();
  while ((Date.now() - start) < ms) {
  }
}

上記のコードでは、「setInterval」は 1000 ミリ秒ごとに 2 を出力する必要があります。ただし、次の sleep ステートメントは完了するまでに 3000 ミリ秒かかるため、setInterval は有効になるまで 3000 ミリ秒まで遅延する必要があります。 setInterval は発効後に累積的な効果を持たないことに注意してください。つまり、一度に 3 つの 2 を出力するのではなく、1 つの 2 だけを出力します。

setTimeout(f, 0)

意味

setTimeout の機能は、指定された時刻までコードの実行を延期することです。指定された時刻が 0 の場合、つまり `setTimeout(f, 0)' はすぐに実行されますか?

答えはノーです。前のセクションで述べたように、現在のスクリプトのすべての同期タスクが処理されるまで、setTimeout で指定されたコールバック関数 f を実行してはなりません。つまり、setTimeout(f, 0) は次のイベント ループの開始時に実行されます。

setTimeout(関数() {
  コンソール.ログ(1);
}, 0);
コンソール.ログ(2);
// 2
// 1

上記のコードは、最初に「2」を出力し、次に「1」を出力します。 「2」は同期タスクであり、イベント ループのこのラウンドで実行され、「1」はイベント ループの次のラウンドで実行されるためです。

つまり、 setTimeout(f, 0) を書く目的は f をできるだけ早く実行することですが、f がすぐに実行されるという保証はありません。

実際、setTimeout(f, 0) は 0 ミリ秒を過ぎると実際には実行されなくなり、ブラウザごとに実装が異なります。 Edge ブラウザを例に挙げると、実行まで 4 ミリ秒待機します。コンピュータがバッテリ電源を使用している場合は、実行に 16 ミリ秒かかります。Web ページが現在のタブ ページにない場合は、実行に 1000 ミリ秒 (1 秒) 遅れます。これはシステム リソースを節約するためです。

応用

setTimeout(f, 0) には非常に重要な用途がいくつかあります。そのアプリケーションの 1 つは、イベントの順序を調整できることです。たとえば、Web 開発では、イベントは最初に子要素で発生し、次に親要素にバブルアップします。つまり、子要素のイベント コールバック関数が親要素のイベント コールバック関数よりも先にトリガーされます。親要素のイベント コールバック関数を最初に発生させたい場合は、setTimeout(f, 0) を使用する必要があります。

// HTMLコードは以下の通り
// <input type="button" id="myButton" value="click">

var input = document.getElementById('myButton');

input.onclick = 関数 A() {
  setTimeout(関数 B() {
    input.value +=' 入力';
  }、0)
};

document.body.onclick = 関数 C() {
  input.value += '本体'
};

ボタンをクリックすると、上記のコードは最初にコールバック関数 A をトリガーし、次に関数 C をトリガーします。関数 A では、setTimeout が関数 B を次のイベント ループ実行まで延期します。これにより、親要素のコールバック関数 C を最初にトリガーするという目的が達成されます。

もう 1 つのアプリケーションはユーザー定義のコールバック関数で、通常はブラウザーのデフォルトのアクションの前にトリガーされます。たとえば、ユーザーが入力ボックスにテキストを入力すると、ブラウザがテキストを受信する前に「keypress」イベントがトリガーされます。したがって、次のコールバック関数はその目的を果たしません。

// HTMLコードは以下の通り
// <input type="text" id="input-box">

document.getElementById('input-box').onkeypress = 関数 (イベント) {
  this.value = this.value.toUpperCase();
}

上記のコードは、ユーザーがテキストを入力するたびに文字をすぐに大文字に変換したいと考えています。しかし、実際には、この入力の前の文字を大文字に変換することしかできません。これは、現時点ではブラウザが新しいテキストを受け取っていないため、this.value は最新の入力文字を取得できないためです。上記のコードは、setTimeout で書き換えられた場合にのみ機能します。

document.getElementById('input-box').onkeypress = function() {
  var self = これ;
  setTimeout(関数() {
    self.value = self.value.toUpperCase();
  }, 0);
}

上記のコードは、ブラウザがテキストを受信した後にトリガーされるようにコードを「setTimeout」に配置します。

setTimeout(f, 0) は、実際にはブラウザの最も早いアイドル期間にタスクが実行されることを意味するため、大量の計算が必要で長時間かかるタスクは、多くの場合、いくつかの小さな部分に分割されます。それぞれを「setTimeout(f, 0)」に入れて実行します。

var div = document.getElementsByTagName('div')[0];

//書き方その1
for (var i = 0xA00000; i < 0xFFFFFF; i++) {
  div.style.backgroundColor = '#' + i.toString(16);
}

//書き方2
var タイマー;
var i=0x100000;

関数 func() {
  タイマー = setTimeout(func, 0);
  div.style.backgroundColor = '#' + i.toString(16);
  if (i++ == 0xFFFFFF) clearTimeout(タイマー);
}

タイマー = setTimeout(func, 0);

上記のコードを記述するには 2 つの方法があり、どちらも Web ページ要素の背景色を変更します。 1 番目の書き方では、JavaScript の実行速度が DOM よりもはるかに速いため、ブラウザの「ブロック」が発生し、多数の DOM 操作が「蓄積」されますが、2 番目の書き方ではブラウザの「ブロック」が発生しません。これは setTimeout(f, 0) の利点です。

この手法を使用するもう 1 つの例は、コードの強調表示です。コード ブロックが大きく、一度にすべてを処理するとパフォーマンスに大きな負荷がかかる可能性がある場合は、コード ブロックを小さなブロックに分割し、一度に 1 ブロックずつ処理します。たとえば、setTimeout(highlightNext, 50) のように記述します。パフォーマンスのプレッシャーが軽減されます。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0