数週間前、私は単一のインタビューから次の質問をツイートしました。
「setTimeoutおよびsetInterval関数のソースコードはどこにありますか? 彼をどこで探しますか? Googleで検索することはできません:) "
***自分で答えてから読んでください***
このツイートへの回答の約半分は間違っていました。 いいえ、ケースはV8(または他のVM)とは関係ありません!!! JavaScript JavaScriptタイマーと呼ばれる
setTimeout
や
setInterval
などの
setTimeout
は、ECMAScript仕様またはJavaScriptエンジン実装の一部ではありません。 タイマー関数はブラウザーレベルで実装されるため、ブラウザーによって実装が異なります。 タイマーもNode.jsランタイム自体にネイティブに実装されます。
ブラウザでは、メインタイマー関数は
Window
インターフェイスに関連しています。これは、他のいくつかの関数やオブジェクトにも関連付けられています。 このインターフェイスは、JavaScriptのメインスコープ内のすべての要素へのグローバルアクセスを提供します。 これが、ブラウザコンソールで
setTimeout
関数を直接実行できる理由です。
Nodeでは、タイマーは
global
オブジェクトの一部であり、
Window
ブラウザインターフェースのように構成されています。 Nodeのタイマーのソースコードを次に示します 。
これはインタビューからの単なる悪い質問であると誰かに思われるかもしれません-そのようなことを知ることはどのような用途ですか?! JavaScript開発者として、私はこのように考えます。V8(および他の仮想マシン)がブラウザーやノードと対話する方法を十分に理解していないことを反対が示す可能性があるため、これを知っている必要があると想定されています。
いくつかの例を見て、いくつかのタイマータスクを解決しましょう。
nodeコマンドを使用して、この記事の例を実行できます。 ここで説明する例のほとんどは、PluralsightのNode.js入門コースで取り上げられました。
遅延関数実行
タイマーは、他の関数の実行を遅延または繰り返すことができる高次関数です(タイマーはそのような関数を最初の引数として受け取ります)。
遅延実行の例を次に示します。
// example1.js setTimeout( () => { console.log('Hello after 4 seconds'); }, 4 * 1000 );
この例では、
setTimeout
を使用して、グリーティングメッセージが4秒間遅延します。
setTimeout
の2番目の引数は遅延(ミリ秒単位)です。 4を得るために4を1000倍します。
setTimeout
の最初の引数は、実行が遅延する関数です。
nodeコマンドで
example1.js
ファイルを実行すると、Nodeは4秒間一時停止し、ウェルカムメッセージを表示します(その後に終了が続きます)。
setTimeout
の最初の引数は関数への単なる参照であることに注意してください。
example1.js
などの組み込み関数であってはなりません。 以下は、組み込み関数を使用しない同じ例です。
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
引数を渡す
遅延に
setTimeout
を使用する
setTimeout
が引数を取る場合、
setTimeout
関数自体の残りの引数(既に学習した2つ後)を使用して、引数の値を遅延関数に転送できます。
// : func(arg1, arg2, arg3, ...) // : setTimeout(func, delay, arg1, arg2, arg3, ...)
以下に例を示します。
// example2.js const rocks = who => { console.log(who + ' rocks'); }; setTimeout(rocks, 2 * 1000, 'Node.js');
上記の
rocks
関数は、2秒遅延し、
who
引数を取り、
setTimeout
を呼び出すと、そのような
who
引数として値「Node.js」を渡します。
node
コマンドで
example2.js
を実行すると、「Node.js rocks」というフレーズが2秒後に表示されます。
タイマータスク#1
したがって、
setTimeout
について既に検討した資料に基づいて、対応する遅延の後に次の2つのメッセージを表示します。
- 「Hello after 4 seconds」というメッセージが4秒後に表示されます。
- 「8秒後にこんにちは」というメッセージが8秒後に表示されます。
制限
ソリューションでは、組み込み関数を含む関数を1つだけ定義できます。 これは、多くの
setTimeout
呼び出しが同じ関数を使用する必要があることを意味します。
解決策
この問題を解決する方法は次のとおりです。
// solution1.js const theOneFunc = delay => { console.log('Hello after ' + delay + ' seconds'); }; setTimeout(theOneFunc, 4 * 1000, 4); setTimeout(theOneFunc, 8 * 1000, 8);
私にとって、
theOneFunc
は
delay
引数を受け取り、画面に表示されるメッセージでこの
delay
引数の値を使用します。 したがって、関数は、遅延の値に応じて異なるメッセージを表示できます。
次に、2つの
setTimeout
呼び出しで
theOneFunc
を使用しました。最初の呼び出しは4秒後に起動され、2番目の呼び出しは8秒後に起動されました。 これらの
setTimeout
呼び出しは両方とも、
theOneFunc
delay
引数を表す3番目の引数も受け取ります。
nodeコマンドで
solution1.js
ファイルを実行すると、タスクの要件が表示されます。さらに、最初のメッセージは4秒後に表示され、2番目は8秒後に表示されます。
機能を繰り返します
しかし、無制限の時間、4秒ごとにメッセージを表示するように要求した場合はどうなりますか?
もちろん、
setTimeout
をループで囲むこともできますが、タイマー関数APIには
setInterval
関数も用意されており、これを使用して任意の操作の「永遠の」実行をプログラムできます。
setInterval
例を次に示し
setInterval
。
// example3.js setInterval( () => console.log('Hello every 3 seconds'), 3000 );
このコードは3秒ごとにメッセージを表示します。
node
コマンドを使用して
example3.js
を実行すると、プロセスを強制終了するまで(CTRL + C)、Nodeはこのコマンドを出力します。
タイマーをキャンセルする
タイマー関数が呼び出されるとアクションが割り当てられるため、このアクションは実行前に元に戻すこともできます。
setTimeout
呼び出しはタイマーIDを返し、
clearTimeout
を呼び出してタイマーをキャンセルするときにこのタイマーIDを使用できます。 以下に例を示します。
// example4.js const timerId = setTimeout( () => console.log('You will not see this one!'), 0 ); clearTimeout(timerId);
この単純なタイマーは0ミリ秒後に(つまり、すぐに)
timerId
しますが、
timerId
の値をキャプチャし、
clearTimeout
を呼び出してこのタイマーを直ちにキャンセルするため、これは発生しません。
node
コマンドで
example4.js
を実行すると、Nodeは何も出力しません-プロセスはただちに終了します。
ちなみに、Node.jsには、
setTimeout
の値を0ミリ秒に設定する別の方法も用意されています。 Node.jsタイマーAPIには
setImmediate
と呼ばれる別の関数があり、基本的に0ミリ秒の値で
setTimeout
と同じことを行いますが、この場合は遅延を省略できます。
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
setImmediate
関数は、すべてのブラウザーでサポートされている
setImmediate
はあり
setImmediate
。 クライアントコードでは使用しないでください。
clearTimeout
とともに、同じことを行う
clearInterval
関数がありますが、
setInerval
呼び出しがあり、
clearImmediate
呼び出しもあります。
タイマー遅延-保証されていないこと
前の例で、0 ms後に
setTimeout
操作を実行すると、この操作はすぐに(
setTimeout
後)発生せず、すべてのスクリプトコードが完全に実行された後(
clearTimeout
呼び出しを含む)にのみ発生します。
例でこの点を明確にしましょう。 0.5秒で動作するはずの単純な
setTimeout
呼び出しを次に示しますが、これは起こりません。
// example5.js setTimeout( () => console.log('Hello after 0.5 seconds. MAYBE!'), 500, ); for (let i = 0; i < 1e10; i++) { // }
この例でタイマーを定義した直後に、大きな
for
ループでランタイム環境を同期的にブロックします。
1e10
の値は1であり、10個のゼロがあるため、サイクルは100億プロセッササイクル続きます(原則として、これは過負荷のプロセッサをシミュレートします)。 このループが完了するまで、ノードは何もできません。
もちろん、実際にはこれは非常に悪いですが、この例は
setTimeout
遅延が保証されているのではなく、 最小値であることを理解するのに役立ちます 。 500 msの値は、遅延が少なくとも500 ms続くことを意味します。 実際、スクリプトは画面にウェルカムラインを表示するのにかなり時間がかかります。 まず、ブロッキングサイクルが完了するまで待つ必要があります。
タイマーの問題#2
「Hello World」メッセージを1秒に1回表示するが、5回だけ表示するスクリプトを作成します。 5回の反復後、スクリプトは「完了」メッセージを表示し、その後ノードプロセスが完了します。
制限 :この問題を解決するとき、
setTimeout
呼び出すことはできません。
ヒント :カウンターが必要です。
解決策
この問題を解決する方法は次のとおりです。
let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000);
counter
の初期値として0を設定してから、idを取得
counter
setInterval
ました。
遅延関数はメッセージを表示し、そのたびにカウンターを1つ増やします。 遅延関数の内部には、ifステートメントがあり、5回の反復が既に通過したかどうかを確認します。 5回の反復後、プログラムは「完了」を表示し、キャプチャされた
intervalId
定数を使用して間隔値をクリアします。 間隔遅延は1000ミリ秒です。
遅延関数を正確に呼び出すのは誰ですか?
JavaScriptを通常の関数内で
this
を使用する場合、たとえば次のようになります。
function whoCalledMe() { console.log('Caller is', this); }
this
値は、 呼び出し元と一致します。 Node REPL内で上記の関数を定義すると、
global
オブジェクトがそれを呼び出します。 ブラウザコンソールで関数を定義すると、
window
オブジェクトがその関数を呼び出します。
関数をオブジェクトのプロパティとして定義して、少しわかりやすくしましょう。
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } }; // : obj.whoCallMe
これで、
obj.whoCallMe
関数を使用してリンクを直接使用すると、
obj
オブジェクト(
id
識別される)が呼び出し元として機能します。
ここでの質問は、
obj.whoCallMe
へのリンクを
obj.whoCallMe
に渡すと、誰が発信者になるのかということです。
// ?? setTimeout(obj.whoCalledMe, 0);
この場合の発信者は誰ですか?
答えは、タイマー機能が実行される場所によって異なります。 この場合、呼び出し元が誰であるかへの依存は、単に受け入れられません。 この場合、関数を呼び出すタイマーの実装に依存するため、呼び出し元の制御が失われます。 Node REPLでこのコードをテストすると、
Timeout
オブジェクトが呼び出し元になります。
注:これは、JavaScript
this
通常の関数内
this
使用される場合にのみ重要です。 矢印関数を使用する場合、呼び出し元はまったく気にしません。
タイマータスク#3
さまざまな遅延で「Hello World」メッセージを継続的に出力するスクリプトを作成します。 1秒の遅延で開始し、各反復で1秒ずつ増やします。 2回目の反復では、遅延は2秒になります。 3番目-3など。
表示されるメッセージに遅延を含めます。 次のようなものが得られるはずです。
Hello World. 1
Hello World. 2
Hello World. 3
...
制限 :変数はconstを使用してのみ定義できます。 letまたはvarを使用することはできません。
解決策
このタスクの遅延時間は変数であるため、ここでは
setInterval
使用できませんが、再帰呼び出し内で
setTimeout
を使用して間隔の実行を手動で構成できます。
setTimeout
最初に実行される
setTimeout
は、次のタイマーを作成します。
さらに、
let
/
var
使用できないため、再帰呼び出しごとに遅延をインクリメントするカウンターを使用できません。 代わりに、再帰関数の引数を使用して、再帰呼び出し中にインクリメントできます。
この問題を解決する方法は次のとおりです。
const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1);
タイマータスク#4
タスク#3と同じ遅延構造を持つ「Hello World」メッセージを表示するスクリプトを作成しますが、今回は5つのメッセージのグループであり、グループにはメインの遅延間隔があります。 5つのメッセージの最初のグループでは、最初の遅延を100ミリ秒、次の場合は200ミリ秒、3番目の場合は300ミリ秒などを選択します。
このスクリプトの仕組みは次のとおりです。
- 100ミリ秒で、スクリプトは「Hello World」を初めて表示し、100ミリ秒の間隔で5回表示します。 最初のメッセージは100ミリ秒後に表示され、2番目は200ミリ秒後に表示されます。
- 最初の5つのメッセージの後、スクリプトはメイン遅延を200ミリ秒増やす必要があります。 したがって、6番目のメッセージは500ミリ秒+ 200ミリ秒(700ミリ秒)後、7番目-900ミリ秒、8番目のメッセージ-1100ミリ秒後などに表示されます。
- 10個のメッセージの後、スクリプトはメイン遅延間隔を300ミリ秒増やす必要があります。 11番目のメッセージは、500ミリ秒+ 1000ミリ秒+ 300ミリ秒(18000ミリ秒)後に表示されます。 12番目のメッセージは2100ミリ秒後に表示されます。
この原則によれば、プログラムは無期限に動作するはずです。
表示されるメッセージに遅延を含めます。 次のようなものが得られるはずです(コメントなし):
Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...
制限事項 :
setInterval
呼び出し(
setTimeout
ではなく)と
if
のみを使用でき
setInterval
。
解決策
setInterval
呼び出しでしか処理できないため、ここでは再帰を使用し、次の
setInterval
呼び出しの遅延を増やす必要があります。 さらに、この再帰関数を5回呼び出した後にのみこれを実行する
if
が必要です。
考えられる解決策は次のとおりです。
let lastIntervalId, counter = 5; const greeting = delay => { if (counter === 5) { clearInterval(lastIntervalId); lastIntervalId = setInterval(() => { console.log('Hello World. ', delay); greeting(delay + 100); }, delay); counter = 0; } counter += 1; }; greeting(100);
それを読んだすべての人に感謝します。