74

いつも私を悩ませてきたのはsetTimeout()、Javascriptのメソッドがどれほど予測不可能かということです。

私の経験では、タイマーは多くの状況でひどく不正確です。不正確とは、実際の遅延時間は250〜500ミリ秒程度変動するように見えることを意味します。これはそれほど時間はかかりませんが、UI要素を非表示/表示するために使用すると、時間が目に見えて目立つ場合があります。

setTimeout()(外部APIに頼ることなく)正確に実行するために実行できるトリックはありますか、それともこれは失われた原因ですか?

4

16 に答える 16

77

setTimeout()(外部 API に頼らずに) が正確に実行されることを保証するために実行できるトリックはありますか、それともこれは失われた原因ですか?

いいえ、いいえ。完全に正確なタイマーに近いものを取得することはできませんsetTimeout()-ブラウザーはそのように設定されていません. ただし、タイミングに関してもそれに依存する必要はありません。ほとんどのアニメーション ライブラリは何年​​も前にこのことを理解していましsetTimeout()(new Date()).milliseconds。これにより、古いブラウザーで適切に動作しながら、新しいブラウザーでより信頼性の高いタイマー サポートを利用できます。

また、あまりにも多くのタイマーを使用することを避けることができます! これは重要です。各タイマーはコールバックです。各コールバックは JS コードを実行します。JS コードの実行中は、ブラウザー イベント (他のコールバックを含む) が遅延または破棄されます。コールバックが終了すると、追加のコールバックが実行の機会を得るために、他のブラウザー イベントと競合する必要があります。したがって、その間隔ですべての保留中のタスクを処理する 1 つのタイマーは、間隔が一致する 2 つのタイマーよりも優れたパフォーマンスを発揮し、(短いタイムアウトの場合) タイムアウトが重複する 2 つのタイマーよりも優れたパフォーマンスを発揮します!

まとめ: setTimeout()「1 つのタイマー / 1 つのタスク」の設計を実装するために使用するのをやめ、リアルタイム クロックを使用して UI アクションをスムーズにします。

于 2008-10-12T21:32:23.833 に答える
34

.

参照; http://www.sitepoint.com/creating-accurate-timers-in-javascript/

このサイトは私を大規模に救済しました。

システム クロックを使用して、タイマーの不正確さを補正できます。一連の setTimeout 呼び出し (各インスタンスが次のインスタンスを呼び出す) としてタイミング関数を実行する場合、精度を維持するために必要なことは、それがどれほど不正確であるかを正確に計算し、次の反復からその差を差し引くことだけです。

var start = new Date().getTime(),  
    time = 0,  
    elapsed = '0.0';  
function instance()  
{  
    time += 100;  
    elapsed = Math.floor(time / 100) / 10;  
    if(Math.round(elapsed) == elapsed) { elapsed += '.0'; }  
    document.title = elapsed;  
    var diff = (new Date().getTime() - start) - time;  
    window.setTimeout(instance, (100 - diff));  
}  
window.setTimeout(instance, 100);  

この方法により、ドリフトが最小限に抑えられ、不正確さが 90% 以上減少します。

それは私の問題を解決しました、それが役立つことを願っています

于 2012-03-06T01:52:09.717 に答える
11

requestAnimationFrame少し前に同様の問題があり、非常に効果的に機能する performance.now() と組み合わせたアプローチを思い付きました。

タイマーを小数点以下約 12 桁まで正確にすることができるようになりました。

    window.performance = window.performance || {};
    performance.now = (function() {
        return performance.now       ||
            performance.mozNow    ||
            performance.msNow     ||
            performance.oNow      ||
            performance.webkitNow ||
                function() {
                    //Doh! Crap browser!
                    return new Date().getTime(); 
                };
        })();

http://jsfiddle.net/CGWGreen/9pg9L/

于 2013-04-19T11:17:51.247 に答える
5

特定の間隔で正確なコールバックを取得する必要がある場合は、次の要点が役立ちます。

https://gist.github.com/1185904

function interval(duration, fn){
  var _this = this
  this.baseline = undefined
  
  this.run = function(){
    if(_this.baseline === undefined){
      _this.baseline = new Date().getTime()
    }
    fn()
    var end = new Date().getTime()
    _this.baseline += duration
 
    var nextTick = duration - (end - _this.baseline)
    if(nextTick<0){
      nextTick = 0
    }
    
    _this.timer = setTimeout(function(){
      _this.run(end)
    }, nextTick)
  }

  this.stop = function(){
    clearTimeout(_this.timer)
  }
}
于 2011-09-01T10:36:11.683 に答える
4

shog9の答えは、私が言うこととほぼ同じですが、UIアニメーション/イベントについて次のように追加します。

画面上にスライドし、下に展開してから内容をフェードインすることになっているボックスがある場合は、3つのイベントすべてを分離して、次々に発生するように遅延させないでください。コールバックを使用してください。最初のイベントはスライドして行われ、エキスパンダーを呼び出します。それが完了すると、フェーダーを呼び出します。jQueryはそれを簡単に行うことができ、他のライブラリも同様に行うことができると確信しています。

于 2008-10-12T23:04:25.880 に答える
3

setTimeout()UI スレッドが必要なタスク (タブの更新や長時間実行スクリプト ダイアログを表示しないなど) に追いつくことができるように、ブラウザにすばやく譲歩するために使用している場合は、 Efficientという新しい API があります。 Script Yielding、別名、setImmediate()それはあなたにとって少しうまくいくかもしれません。

setImmediate()は と非常によく似た動作setTimeout()をしますが、ブラウザが他に何もすることがない場合はすぐに実行できます。setTimeout(..., 16)orを使用している多くの状況(つまりsetTimeout(..., 4)setTimeout(..., 0)未処理の UI スレッド タスクをブラウザーに実行させ、Long Running Script ダイアログを表示させたくない) では、単純に を に置き換えてsetTimeout()setImmediate()2 番目 (ミリ秒) の引数を削除することができます。

との違いsetImmediate()は、基本的に利回りであることです。ブラウザーが UI スレッドで行う必要がある場合 (たとえば、タブの更新)、コールバックに戻る前にそれを行います。ただし、ブラウザーが既にすべての作業に追いついている場合、 で指定されたコールバックsetImmediate()は基本的に遅延なく実行されます。

残念ながら、他のブラウザー ベンダーからの反発があるため、現時点では IE9+ でのみサポートされています。

ただし、それを使用したい場合や、他のブラウザーがある時点でそれを実装することを望む場合は、利用可能な優れたポリフィルがあります。

setTimeout()animationを使用している場合は、コードがモニターのリフレッシュ レートと同期して実行されるため、requestAnimationFrameが最適です。

setTimeout()より遅い cadenceで使用している場合、たとえば 300 ミリ秒ごとに 1 回、user1213320 が提案するものと同様のソリューションを使用できます。ここでは、タイマーが実行された最後のタイムスタンプからの時間を監視し、遅延を補正します。改善点の 1 つは、現在の時刻のミリ秒を超える解像度を取得する代わりに、新しいHigh Resolution Timeインターフェイス (aka ) を使用できることです。window.performance.now()Date.now()

于 2014-01-14T15:38:16.807 に答える
2

目標時間に「忍び寄る」必要があります。多少の試行錯誤は必要ですが、本質的には.

必要な時間の約 100 ミリ秒前に完了するようにタイムアウトを設定します

タイムアウト ハンドラ関数を次のようにします。

calculate_remaining_time
if remaining_time > 20ms // maybe as much as 50
  re-queue the handler for 10ms time
else
{
  while( remaining_time > 0 ) calculate_remaining_time;
  do_your_thing();
  re-queue the handler for 100ms before the next required time
}

ただし、 while ループは他のプロセスによって中断される可能性があるため、まだ完全ではありません。

于 2010-09-06T14:20:04.327 に答える
2

Shog9 の提案をデモする例を次に示します。これにより、jquery プログレス バーが 6 秒間スムーズに塗りつぶされ、塗りつぶされると別のページにリダイレクトされます。

var TOTAL_SEC = 6;
var FRAMES_PER_SEC = 60;
var percent = 0;
var startTime = new Date().getTime();

setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);

function updateProgress() {
    var currentTime = new Date().getTime();

    // 1000 to convert to milliseconds, and 100 to convert to percentage
    percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100;

    $("#progressbar").progressbar({ value: percent });

    if (percent >= 100) {
        window.location = "newLocation.html";
    } else {
        setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
    }                 
}
于 2013-03-08T01:16:33.053 に答える
0

それを言うのは嫌いですが、これを軽減する方法はないと思います。ただし、クライアントシステムに依存すると思いますので、JavaScriptエンジンやマシンを高速化すると、少し正確になる可能性があります。

于 2008-10-12T20:45:58.453 に答える
0

私の経験では、jsが動作することを認識した最短の妥当な時間は約32〜33ミリ秒ですが、それは労力を失っています。..。

于 2008-10-12T20:46:54.083 に答える
0

ここには間違いなく制限があります。いくつかの見通しを与えるために、GoogleがリリースしたばかりのChromeブラウザは15〜20ミリ秒でsetTimeout(function(){}、0)を実行できるほど高速ですが、古いJavascriptエンジンはその関数を実行するのに数百ミリ秒かかりました。setTimeoutはミリ秒を使用しますが、現時点では、その精度でコードを実行できるjavascript仮想マシンはありません。

于 2008-10-12T20:50:29.910 に答える
0

JavaScript タイムアウトの事実上の制限は 10 ~ 15 ミリ秒です (実際の js 実行を 185 ミリ秒実行していない限り、200 ミリ秒を取得するために何をしているのかわかりません)。これは、Windows が 15 ミリ秒の標準タイマー解像度を持っているためです。これを改善する唯一の方法は、システム全体の設定である Windows の高解像度タイマーを使用することです。そのため、システム上の他のアプリケーションを台無しにし、バッテリーの寿命を延ばすこともできます (Chrome はこの問題に関する Intel のバグ)。

事実上の標準である 10 ~ 15 ミリ秒は、Web サイトで 0 ミリ秒のタイムアウトを使用しているが、10 ~ 15 ミリ秒のタイムアウトを想定した方法でコーディングしているためです (たとえば、60 fps を想定しているが、デルタ ロジックなしで 0 ミリ秒/フレームを要求する js ゲーム。ゲーム/サイト/アニメーションは、意図したよりも数桁速くなります)。これを考慮して、Windows のタイマーの問題がないプラットフォームでも、ブラウザーはタイマーの解像度を 10 ミリ秒に制限しています。

于 2008-10-12T22:13:09.430 に答える
0

ダン、私の経験から (時間管理が主題である JavaScript での SMIL2.1 言語の実装を含む)、setTimeout や setInterval の高精度を実際に必要としないことを保証できます。

ただし、重要なのは、キューに入れられたときに setTimeout/setInterval が実行される順序です。これは常に完全に機能します。

于 2008-10-12T21:09:09.700 に答える
0

これが私が使用するものです。JavaScript であるため、Frontend と node.js の両方のソリューションを投稿します。

どちらの場合も、同じ小数丸め関数を使用します。理由は次のとおりです。

const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)

places- 丸める小数点以下の桁数。これは安全であり、浮動小数点数の問題を回避する必要があります (1.0000000000005~ のような数値は問題になる可能性があります)。ミリ秒に変換された高解像度タイマーによって提供される小数を丸める最良の方法を研究するのに時間を費やしました。

that + symbol- オペランドを数値に変換する単項演算子です。Number()

ブラウザ

const start = performance.now()

// I wonder how long this comment takes to parse

const end = performance.now()

const result = (end - start) + ' ms'

const adjusted = round(2, result) // see above rounding function

node.js

// Start timer
const startTimer = () => process.hrtime()

// End timer
const endTimer = (time) => {
    const diff = process.hrtime(time)
    const NS_PER_SEC = 1e9
    const result = (diff[0] * NS_PER_SEC + diff[1])
    const elapsed = Math.round((result * 0.0000010))
    return elapsed
}

// This end timer converts the number from nanoseconds into milliseconds;
// you can find the nanosecond version if you need some seriously high-resolution timers.

const start = startTimer()

// I wonder how long this comment takes to parse

const end = endTimer(start)

console.log(end + ' ms')
于 2017-09-29T08:01:50.563 に答える