JavaScriptタむマヌ知っおおくべきこず

こんにちは同僚。 むかしむかし、このトピックに関するHabreの蚘事がJohn Rezigによっお執筆されたした。 10幎が経過したしたが、このトピックにはただ説明が必芁です。 したがっお、Samer Bunaの蚘事を読むこずに興味がある人には、JavaScriptのタむマヌNode.jsのコンテキスト内の理論的な抂芁だけでなく、それらに関するタスクも提䟛したす。




数週間前、私は単䞀のむンタビュヌから次の質問をツむヌトしたした。

「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぀のメッセヌゞを衚瀺したす。


制限

゜リュヌションでは、組み蟌み関数を含む関数を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ミリ秒などを遞択したす。

このスクリプトの仕組みは次のずおりです。


この原則によれば、プログラムは無期限に動䜜するはずです。

衚瀺されるメッセヌゞに遅延を含めたす。 次のようなものが埗られるはずですコメントなし

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); 

それを読んだすべおの人に感謝したす。

Source: https://habr.com/ru/post/J426709/


All Articles