ホームページ > 記事 > ウェブフロントエンド > JavaScript 非同期イベントポーリングの詳細な分析
この記事では、JavsScript の非同期イベント ポーリングについて詳しく説明します。必要な方は参考にしてください。
JavsScript はシングルスレッドのプログラミング言語です。つまり、JavaScript エンジンは 1 つのスレッドで 1 つのことしか処理できません。一度に。
シングル スレッドでは、同時実行性によって生じる問題についてあまり心配する必要がないため、プログラミング コードが簡素化されますが、ネットワーク リクエストなどの長期的な操作を実行することにもなります。メインスレッドをブロックしている間。
状況によっては、サーバーがリクエストの処理に時間がかかり、メインスレッドがブロックされ、Web ページが長時間応答しない状態になることを想像してください。
これが、非同期 JavaScript が導入された理由です。非同期 JavaScript (コールバック関数、Promise、async/await など) を使用すると、メイン スレッドをブロックすることなくネットワーク リクエストを長時間実行できます:)
非同期 JavasScript がどのように機能するかはご存知かもしれませんが、そうではありません。しかし、それがどのように機能するかを知ることは、JavaScript の非同期についてより深く理解するのに役立ちます。
それでは、早速、始めましょう :)
非同期 JavaScript
について詳しく説明する前に、まず同期 JavaScript
コードが JavaScript
エンジンでどのように実行されるかを理解しましょう。例:
const second = () => { console.log('Hello there!'); } const first = () => { console.log('Hi there!'); second(); console.log('The End'); } first();
上記のコードが JavaScript エンジンでどのように実行されるかを理解するには、実行コンテキストと呼び出しスタック (実行スタックとも呼ばれる) の概念を理解する必要があります。
関数コードは関数実行コンテキストで実行され、グローバル コードはグローバル実行コンテキストで実行されます。各関数には独自の実行コンテキストがあります。
コール スタックは、その名前が示すように、コードの実行中に作成されるすべての実行コンテキストを格納するために使用される LIFO (後入れ先出し) 構造を持つスタックです。
JavaScript はシングルスレッド プログラミング言語であるため、呼び出しスタックは 1 つだけです。呼び出しスタックには LIFO 構造があり、項目はスタックの最上位にのみ追加または削除できることを意味します。
上記のコード スニペットに戻って、JavaScript エンジンでコードがどのように実行されるかを理解してみましょう。
const second = () => { console.log('Hello there!'); } const first = () => { console.log('Hi there!'); second(); console.log('The End'); } first();
このコードが実行されると、グローバル実行コンテキストが (main() によって) 作成されます。を表します) を呼び出し、呼び出しスタックの先頭にプッシュします。 first() の呼び出しが発生すると、その呼び出しはスタックの一番上にプッシュされます。
次に、console.log('Hi there!') がスタックの最上位にプッシュされ、完了するとスタックからポップされます。その後、 Second() を呼び出します。これにより、 Second() 関数がスタックの先頭にプッシュされます。
console.log('Hello there!') はスタックの最上位にプッシュされ、完了するとスタックからポップされます。 Second() 関数が終了すると、スタックからポップされます。
console.log("the End") はスタックの一番上にプッシュされ、完了すると削除されます。その後、first() 関数が完了するため、スタックから削除されます。
プログラムはこの時点で実行を完了しているため、グローバル実行コンテキスト (main()) がスタックからポップされます。
コール スタックと同期 JavaScript の仕組みについて基本を理解したところで、非同期 JavaScript の話に戻りましょう。
画像処理またはネットワーク リクエストを同期的に実行していると仮定します。例:
const processImage = (image) => { /** * doing some operations on image **/ console.log('Image processed'); } const networkRequest = (url) => { /** * requesting network resource **/ return someData; } const greeting = () => { console.log('Hello World'); } processImage(logo.jpg); networkRequest('www.somerandomurl.com'); greeting();
画像処理とネットワーク リクエストの実行には時間がかかり、processImage() 関数が呼び出されるときは、画像のサイズに応じて時間がかかります。
processImage() 関数が完了すると、スタックから削除されます。次に、networkRequest() 関数が呼び出され、スタックにプッシュされます。同様に、実行が完了するまでにも時間がかかります。
最後に、networkRequest() 関数が完了すると、greeting() 関数が呼び出されます。これは、この関数にはコンソールしか含まれていないためです。ステートメントとコンソールをログに記録します。通常、ログ ステートメントは高速であるため、greeting() 関数が実行され、すぐに返されます。
したがって、関数 (processImage() や networkRequest() など) が完了するまで待つ必要があります。これは、これらの関数がコール スタックまたはメイン スレッドをブロックすることを意味します。したがって、上記のコードの実行中は他の操作を実行できませんが、これは理想的ではありません。
最も単純な解決策は、非同期コールバックです。コードをノンブロッキングにするために非同期コールバックを使用します。例:
const networkRequest = () => { setTimeout(() => { console.log('Async Code'); }, 2000); }; console.log('Hello World'); networkRequest();
ここでは、setTimeout メソッドを使用してネットワーク要求をシミュレートします。 setTimeout は JavaScript エンジンの一部ではなく、Web API (ブラウザー内) および C/C API (node.js 内) の一部であることに注意してください。
このコードがどのように実行されるかを理解するには、イベント ポーリングやコールバック キュー (またはメッセージ キュー) などの概念をさらに理解する必要があります。
事件轮询、web api和消息队列不是JavaScript引擎的一部分,而是浏览器的JavaScript运行时环境或Nodejs JavaScript运行时环境的一部分(对于Nodejs)。在Nodejs中,web api被c/c++ api所替代。
现在让我们回到上面的代码,看看它是如何异步执行的。
const networkRequest = () => { setTimeout(() => { console.log('Async Code'); }, 2000); }; console.log('Hello World'); networkRequest(); console.log('The End');
当上述代码在浏览器中加载时,console.log(' Hello World ') 被推送到堆栈中,并在完成后弹出堆栈。接下来,将遇到对 networkRequest() 的调用,因此将它推到堆栈的顶部。
下一个 setTimeout() 函数被调用,因此它被推到堆栈的顶部。setTimeout()有两个参数:
1) 回调和
2) 以毫秒(ms)为单位的时间。
setTimeout() 方法在web api环境中启动一个2s的计时器。此时,setTimeout()已经完成,并从堆栈中弹出。cosole.log(“the end”) 被推送到堆栈中,在完成后执行并从堆栈中删除。
同时,计时器已经过期,现在回调被推送到消息队列。但是回调不会立即执行,这就是事件轮询开始的地方。
事件轮询的工作是监听调用堆栈,并确定调用堆栈是否为空。如果调用堆栈是空的,它将检查消息队列,看看是否有任何挂起的回调等待执行。
在这种情况下,消息队列包含一个回调,此时调用堆栈为空。因此,事件轮询将回调推到堆栈的顶部。
然后是 console.log(“Async Code”) 被推送到堆栈顶部,执行并从堆栈中弹出。此时,回调已经完成,因此从堆栈中删除它,程序最终完成。
消息队列还包含来自DOM事件(如单击事件和键盘事件)的回调。例如:
document.querySelector('.btn').addEventListener('click',(event) => { console.log('Button Clicked'); });
对于DOM事件,事件侦听器位于web api环境中,等待某个事件(在本例中单击event)发生,当该事件发生时,回调函数被放置在等待执行的消息队列中。
同样,事件轮询检查调用堆栈是否为空,并在调用堆栈为空并执行回调时将事件回调推送到堆栈。
我们还可以使用setTimeout
来延迟函数的执行,直到堆栈清空为止。例如
const bar = () => { console.log('bar'); } const baz = () => { console.log('baz'); } const foo = () => { console.log('foo'); setTimeout(bar, 0); baz(); } foo();
打印结果:
foo baz bar
当这段代码运行时,第一个函数foo()被调用,在foo内部我们调用console.log('foo'),然后setTimeout()被调用,bar()作为回调函数和时0秒计时器。
现在,如果我们没有使用 setTimeout, bar() 函数将立即执行,但是使用 setTimeout 和0秒计时器,将bar的执行延迟到堆栈为空的时候。
0秒后,bar()回调被放入等待执行的消息队列中。但是它只会在堆栈完全空的时候执行,也就是在baz和foo函数完成之后。
我们已经了解了异步回调和DOM事件是如何执行的,它们使用消息队列存储等待执行所有回调。
ES6引入了任务队列的概念,任务队列是 JavaScript 中的 promise 所使用的。消息队列和任务队列的区别在于,任务队列的优先级高于消息队列,这意味着任务队列中的promise 作业将在消息队列中的回调之前执行,例如:
const bar = () => { console.log('bar'); }; const baz = () => { console.log('baz'); }; const foo = () => { console.log('foo'); setTimeout(bar, 0); new Promise((resolve, reject) => { resolve('Promise resolved'); }).then(res => console.log(res)) .catch(err => console.log(err)); baz(); }; foo();
打印结果:
foo baz Promised resolved bar
我们可以看到 promise 在 setTimeout 之前执行,因为 promise 响应存储在任务队列中,任务队列的优先级高于消息队列。
因此,我们了解了异步 JavaScript 是如何工作的,以及调用堆栈、事件循环、消息队列和任务队列等概念,这些概念共同构成了 JavaScript 运行时环境。虽然成为一名出色的JavaScript开发人员并不需要学习所有这些概念,但是了解这些概念是有帮助的:)
以上がJavaScript 非同期イベントポーリングの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。