JavaScriptについて詳しく学びたい場合は、次の記事をご覧ください。役に立つかもしれません。
まえがき
優れたフロントエンドエンジニアになることに興味がある場合、または JavaScript を深く学びたい場合、非同期プログラミングは必須の知識点であり、初級、中級、上級を区別するための基礎の 1 つでもあります。フロントエンド。非同期プログラミングの明確な概念がない場合は、時間をかけて JavaScript 非同期プログラミングを学習することをお勧めします。非同期プログラミングについて独自の理解を持っている場合は、この記事を読んで一緒にコミュニケーションをとることを歓迎します。
同期と非同期
非同期を導入する前に、いわゆる同期プログラミングとは、コンピューターがコードを 1 行ずつ順番に実行することを意味し、時間のかかる現在のコード タスクの実行により、後続のコードの実行がブロックされることを確認してください。
同期プログラミングは、典型的なリクエスト/レスポンス モデルです。リクエストが関数またはメソッドを呼び出すと、そのレスポンスが返されるのを待ってから、後続のコードを実行する必要があります。
通常の状況では、同期プログラミングではコードが順番に実行されるため、プログラムの実行が確実に行われますが、ファイルの内容の読み取りやサーバー インターフェイス データの要求などの一部のシナリオでは、返す必要があります。同期プログラミングで実装すると、その後の処理、ファイルの読み込み、インターフェースのリクエストに時間がかかり、同期プログラミングで実装すると時間がかかります。データが返されるのを待っている間、ページの操作やスクロールなどの操作もブロックされることは明らかであり、これはまさに非同期プログラミングの才能を発揮する必要があるシナリオです。以下では、時間のかかるタスク A がブロックされます。タスク B の実行については、タスク A が完了するまで待ってから B の実行を続行します。
非同期プログラミングを使用する場合、待機する前に後続のコードの実行を続けることができます。現在のタスクの応答が返されるまで、つまり、現在の実行タスクは後続の実行をブロックしません。
非同期プログラミングは、同期プログラミングのリクエスト/レスポンスモードとは異なり、リクエストが関数やメソッドを呼び出した後、すぐにレスポンスを待つ必要はありません。他のタスクを実行するために、ステータス、通知、コールバックを返した後に前のタスクの応答を渡して呼び出し元に通知することができます。
マルチスレッド
前に説明したように、非同期プログラミングは同期プログラミングにおけるブロックの問題を効果的に解決できます。では、非同期プログラミングを実装するにはどのような方法があるでしょうか?非同期実装を実現する通常の方法は、C# などのマルチスレッドです。つまり、時間のかかるタスク A を実行しながら、複数のスレッドを同時に起動し、異なる操作を並行して実行できます。 、タスク B はスレッド 2 で実行することもできます:
JavaScript シングルスレッド
JavaScript 言語の実行環境はシングルスレッドであり、単一のスレッドがプログラムを実行する場合、必要なプログラム パスは連続した順序で配置されます。非同期実装を使用する場合、最初のタスクは、後のタスクが実行される前に処理される必要があります。複数のタスクを同時に実行できます。では、JavaScript で非同期プログラミングを実装するにはどうすればよいでしょうか? 次のセクションでは、その非同期メカニズムについて詳しく説明します。
並列と同時実行
前述したように、マルチスレッドタスクは並列実行できますが、JavaScript シングルスレッド非同期プログラミングはマルチタスクの同時実行を実現できます。並列処理と同時実行の違いを説明する必要があります。
通常は 1 つのタスクのみが実行されます。同時接続数は、ブラウザがサーバーへのリクエストを開始して TCP 接続を確立するときに、サーバーによって 1 秒あたりに確立される接続の合計数を指します。サーバーが 10 ミリ秒で 1 つの接続を処理できる場合、同時接続数は次のようになります。 100。
JavaScript 非同期メカニズム
このセクションでは、JavaScript 非同期メカニズムを紹介します。まず例を見てみましょう: for (var i = 0; i < 5; i ++) {
setTimeout(function(){
console.log(i);
}, 0);
}
console.log(i);
//5 ; 5 ; 5 ; 5; 5 ログイン後にコピー 最終出力はすべて 5 であることを理解する必要があります: iここでは、for が含まれるコンテキストの変数を示します。ループが見つかり、そこにあります そして i は 1 つだけです; i==5 がループの終わりにあります; JavaScript シングルスレッド イベント プロセッサは、スレッドがアイドル状態になるまで次のイベントを実行しません。
上記の 3 番目のポイントで述べたように、上記の例の setTimeout() と JavaScript の非同期メカニズムを真に理解したい場合は、JavaScript イベント ループと同時実行モデルを理解する必要があります。 同時実行モデル現時点では、JavaScript が非同期タスクを実行するときに、応答が返されるまで待つ必要がなく、応答が返されると通知され、コールバックが行われて他のタスクの実行を続行できることがすでにわかっています。またはイベントハンドラが実行されます。では、これらすべては正確にどのように行われ、どのようなルールや順序で機能するのでしょうか?次に、この質問に答える必要があります。 注: コールバックとイベント ハンドラーの間には基本的に違いはなく、状況によって呼び方が異なるだけです。 前述したように、JavaScript 非同期プログラミングでは複数のタスクを同時に実行できますが、この機能を実現する基盤は、JavaScript がイベント ループに基づく同時実行モデルを持っていることです。 スタックとキュー JavaScript 同時実行モデルを紹介する前に、スタックとキューの違いを簡単に紹介します。 ヒープ: メモリ内のブロックされていない領域。通常はオブジェクト (参照型) を格納します。データ構造は後入れ先出しの順序で保存され、通常は関数パラメーターと基本的な型の値の変数 (値によってアクセスされます) を保存します。 。 イベントループ JavaScript エンジンは JavaScript コードの解析と実行を担当しますが、単独で実行することはできません。通常はホスト環境 (通常はブラウザーまたはノードサーバー) を必要とし、前述の単一スレッドの作成を指します。これらのホスト環境では、JavaScript エンジンを呼び出して複数の JavaScript コード ブロックのスケジュールと実行を実行するメカニズムが提供されます (はい、JavaScript コードはブロックで実行されます)。このメカニズムはイベント ループ (イベント ループ) と呼ばれます。 。 注:这里的事件与DOM事件不要混淆,可以说这里的事件包括DOM事件,所有异步操作都是一个事件,诸如ajax请求就可以看作一个request请求事件。 JavaScript执行环境中存在的两个结构需要了解: 注:关于全局代码,由于所有的代码都是在全局上下文执行,所以执行栈顶总是全局上下文就很容易理解,直到所有代码执行完毕,全局上下文退出执行栈,栈清空了;也即是全局上下文是第一个入栈,最后一个出栈。 任务分析事件循环流程前,先阐述两个概念,有助于理解事件循环:同步任务和异步任务。 任务很好理解,JavaScript代码执行就是在完成任务,所谓任务就是一个函数或一个代码块,通常以功能或目的划分,比如完成一次加法计算,完成一次ajax请求;很自然的就分为同步任务和异步任务。同步任务是连续的,阻塞的;而异步任务则是不连续,非阻塞的,包含异步事件及其回调,当我们谈及执行异步任务时,通常指执行其回调函数。 事件循环流程关于事件循环流程分解如下: 宿主环境为JavaScript创建线程时,会创建堆(heap)和栈(stack),堆内存储JavaScript对象,栈内存储执行上下文; 栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息; 当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及回调); 当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈; 当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息(next tick,事件循环流转一次称为一次tick)。
使用代码可以描述如下: var eventLoop = []; var event; var i = eventLoop.length - 1; // 后进先出
while(eventLoop[i]) { event = eventLoop[i--];
if (event) { // 事件回调存在
event();
} // 否则事件消息被丢弃
} ログイン後にコピー 这里注意的一点是等待下一个事件消息的过程是同步的。 并发模型与事件循环 var ele = document.querySelector('body'); function clickCb(event) { console.log('clicked');
} function bindEvent(callback) {
ele.addEventListener('click', callback);
}
bindEvent(clickCb); ログイン後にコピー 针对如上代码我们可以构建如下并发模型: 如上图,当执行栈同步代码块依次执行完直到遇见异步任务时,异步任务进入等待状态,通知线程,异步事件触发时,往消息队列插入一条事件消息;而当执行栈后续同步代码执行完后,读取消息队列,得到一条消息,然后将该消息对应的异步任务入栈,执行回调函数;一次事件循环就完成了,也即处理了一个异步任务。 再谈JavaScript の同期および非同期プログラミングの例の使用法について了解了JavaScript事件循环后我们再看前文关于JavaScript の同期および非同期プログラミングの例の使用法について 的例子就比较清晰了: JavaScript の同期および非同期プログラミングの例の使用法について 所表达的意思是:等待0秒后(这个时间由第二个参数值确定),往消息队列插入一条定时器事件消息,并将其第一个参数作为回调函数;而当执行栈内同步任务执行完毕时,线程从消息队列读取消息,将该异步任务入栈,执行;线程空闲时再次从消息队列读取消息。
再看一个实例: var start = +new Date(); var arr = [];
setTimeout(function(){ console.log('time: ' + (new Date().getTime() - start));
},10); for(var i=0;i<=1000000;i++){
arr.push(i);
} ログイン後にコピー 执行多次输出如下:
在setTimeout 异步回调函数里我们输出了异步任务注册到执行的时间,发现并不等于我们指定的时间,而且两次时间间隔也都不同,考虑以下两点:
所以异步执行时间不精确是必然的,所以我们有必要明白无论是同步任务还是异步任务,都不应该耗时太长,当一个消息耗时太长时,应该尽可能的将其分割成多个消息。
Web Workers
每个Web Worker或一个跨域的iframe都有各自的堆栈和消息队列,这些不同的文档只能通过postMessage方法进行通信,当一方监听了message事件后,另一方才能通过该方法向其发送消息,这个message事件也是异步的,当一方接收到另一方通过postMessage方法发送来的消息后,会向自己的消息队列插入一条消息,而后续的并发流程依然如上文所述。
JavaScript异步实现
关于JavaScript的异步实现,以前有:回调函数,发布订阅模式,Promise三类,而在ES6中提出了生成器(Generator)方式实现,关于回调函数和发布订阅模式实现可参见另一篇文章,后续将推出一篇详细介绍Promise和Generator。
以上就是javascript同步与异步的全部内容了,感谢大家的阅读。
|