まず、Interval 関数に何を期待するかを自問する必要があると思います。
コンテキストを維持する必要があります。カウンターを確実にインクリメントできない間隔は、非常に悲惨です。
関数内にあるものは何でも実行する必要があり、実行間隔よりも優先されます (ここでも同じです。再度インクリメントする前にタイマーを上げなければなりません)。
js コードの残りの部分とは別の実行スタックを持つ必要があります (残りの部分が実行を開始するまで、これらのタイマーがビジネスを終了するのを待ちたくありません)。
彼らは、それがどれほど広大であっても、自分がいるコンテキストを認識している必要があります (たとえば、タイマー関数内で jQuery を使用できるようにしたい)。
したがって、Matt Greerが上で述べたように、間隔は設計上正確ではありません。これは主に、間隔が正確であると実際には期待していないが、特定の時間にコードを確実に実行することを期待しているためです。
Chromium の実装を見ると、 setTimeout と setInterval の実装がDOMTimer::installに基づいていることがわかります。これには、実行コンテキスト、アクション、およびタイマーが単発の場合が渡されます。
これは、RunloopTimer に渡され、システム タイマーの助けを借りてループを実行します(こちらを参照)。
(ちなみに、クロムは間隔に最小 10 ミリ秒をインストールします。ここで確認できます)
アクションのすべての実行はここで処理されます。これは、最後の実行が特定のタイミング制限を超えた、または下回ってから経過した時間にわたってアサーションを行いません。
反対に、タイマーのネスティング レベルが深くなりすぎないことを主張するだけです。これは、リソースを過剰に使用しているタイマーや、指定された間隔で実行が遅すぎるタイマーを遅くすることによって行われます。
if (m_nestingLevel >= maxTimerNestingLevel)
augmentRepeatInterval(minimumInterval - repeatInterval());
}
AugmentRepeatInterval は、間隔にミリ秒を追加するだけです。
void augmentRepeatInterval(double delta) { augmentFireInterval(delta); m_repeatInterval += delta; }
それで、私たちは何を結論付けることができますか?
インターバルまたはタイムアウトの時間精度を測定するのは時間の無駄です。気にする必要があり、気にすることができるのは、関数で実行したいことに対して間隔をあまり低く設定しないことです。ブラウザは、間隔とタイムアウトをタイムリーに実行するために最善を尽くしますが、正確なタイミングを保証するものではありません。
間隔の実行は、環境、ブラウザー、実装、バージョン、コンテキスト、アクション自体などに依存します。正確であることを意図したものではなく、setTimeout や setInterval を使用して正確に何かをプログラムしたい場合は、今気が狂っているか、後で狂ってしまうかのどちらかです。
あなたのコードでは、関数に重い実行を追加すると、タイマーがより正確になったと言いました。これにはさまざまな理由が考えられます (おそらく、より多くのメモリ、より多くの排他的 CPU 時間、より多くのワーカーなどを取得します)。私はそれに非常に興味がありますが、重い実行を行ったコードをまだ提供していません。そのため、それに関する回答が必要な場合は、コードを提供してください。コードなしでは何も想定するのが難しいためです。
しかし、インターバルをよりタイムリーに実行することが何であれ、それは決して信頼できるものではありません. 異なるシステムで測定を開始すると、あらゆる種類のさまざまな結果が得られる可能性が高くなります。
アップデート
それ以外に、何も結果をもたらさないコードは、ブラウザの js エンジンによって適切に最適化される (実行されない、1 回だけ実行される、より適切な方法で実行される) 場合があります。あなたの例に基づいた例を挙げましょう(クロムで実行されたすべてのもの):
function start() {
window.setInterval(function() {
update();
}, 1);
}
lastTime = new Date;
numFrames = 0;
lastFrames = 0;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
for (var i=0; i < 1000000; i++) { var k = 'string' + 'string' + 'string' }
}
ヒット後の最初の実行にstart
は時間がかかりますが、それ以降の実行には時間がかかりません (少なくとも Webkit では)。これは、反復内のコードが何も変更されず、ブラウザが最初の実行後にそれを認識し、単純にそれ以上実行しないためです。
実行が外部変数へのバインディングを維持する必要がある場合 (k
この場合) 、それがどのように変化するかを見てみましょう。
var k;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
for (var i=0; i < 1000000; i++) { k = 'string' + 'string' + 'string' }
}
OK、ここでは実行時間に多少の影響がありますが、それでもかなり高速です。ブラウザーは、for ループが常に同じことを行うことを認識していますが、一度だけ実行します。たとえば、反復によって実際に巨大な文字列が作成された場合はどうなるでしょうか。
var k;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
k = '';
for (var i=0; i < 1000000; i++) { k += i.toString() }
}
何百万文字もの文字列を返さなければならないため、これはブラウザーを苦境に陥れます。それをもっと苦しめることはできますか?
var k;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
k = '';
for (var i=0; i < 1000000; i++) { k = ['hey', 'hey', 'hey'].join('') }
}
この配列の連結は最適化できず、ほとんどすべてのブラウザをゆっくりと苦痛に満ちたものにします。
したがって、あなたの場合、重い実行により、より多くのメモリが予約され、オプティマイザによって即座に解放される可能性があります。新鮮な空気の息吹と余分なメモリとアイドル状態の CPU が関数を喜んでジャンプさせたのかもしれませんが、私が言ったように、重い実行コードを見ない限り、信頼できるものは何もありません。