av一区二区在线观看_亚洲男人的天堂网站_日韩亚洲视频_在线成人免费_欧美日韩精品免费观看视频_久草视

您的位置:首頁技術文章
文章詳情頁

JavaScript異步編程

瀏覽:15日期:2023-11-14 08:13:59

還記得一年前寫過一篇關于 JavaScript異步編程簡述 的文章,主要介紹了JavaScript的單線程特性與異步編程實現方式:

回調函數,發布訂閱模式,Promise對象三種,關于Promise介紹的比較簡略,決定再詳細總結一下,既是對上一篇文章的補充,也能以更深刻的方式分享自己關于異步編程的理解。

前言

如果你有志于成為一個優秀的前端工程師,或是想要深入學習JavaScript,異步編程是必不可少的一個知識點,這也是區分初級,中級或高級前端的依據之一。如果你對異步編程沒有太清晰的概念,那么我建議你花點時間學習JavaScript異步編程,如果你對異步編程有自己的獨特理解,也歡迎閱讀本文,一起交流。

同步與異步

介紹異步之前,回顧一下,所謂同步編程,就是計算機一行一行按順序依次執行代碼,當前代碼任務耗時執行會阻塞后續代碼的執行。

同步編程,即是一種典型的請求-響應模型,當請求調用一個函數或方法后,需等待其響應返回,然后執行后續代碼。

一般情況下,同步編程,代碼按序依次執行,能很好的保證程序的執行,但是在某些場景下,比如讀取文件內容,或請求服務器接口數據,需要根據返回的數據內容執行后續操作,讀取文件和請求接口直到數據返回這一過程是需要時間的,網絡越差,耗費時間越長,如果按照同步編程方式實現,在等待數據返回這段時間,JavaScript是不能處理其他任務的,此時頁面的交互,滾動等任何操作也都會被阻塞,這顯然是及其不友好,不可接受的,而這正是需要異步編程大顯身手的場景,如下圖,耗時任務A會阻塞任務B的執行,等到任務A執行完才能繼續執行B:

JavaScript異步編程

當使用異步編程時,在等待當前任務的響應返回之前,可以繼續執行后續代碼,即當前執行任務不會阻塞后續執行。

異步編程,不同于同步編程的請求-響應模式,其是一種 事件驅動編程 ,請求調用函數或方法后,無需立即等待響應,可以繼續執行其他任務,而之前任務響應返回后可以通過狀態、通知和回調來通知調用者。

多線程

前面說明了異步編程能很好的解決同步編程阻塞的問題,那么實現異步的方式有哪些呢?通常實現異步方式是多線程,如C#, 即同時開啟多個線程,不同操作能并行執行,如下圖,耗時任務A執行的同時,在線程二中任務B也可以執行:

JavaScript異步編程

JavaScript單線程

JavaScript語言執行環境是單線程的,單線程在程序執行時,所走的程序路徑按照連續順序排下來,前面的必須處理好,后面的才會執行,而使用異步實現時,多個任務可以并發執行。那么JavaScript的異步編程如何實現呢,下一節將詳細闡述其異步機制。

并行與并發

前文提到多線程的任務可以并行執行,而JavaScript單線程異步編程可以實現多任務并發執行,這里有必要說明一下并行與并發的區別。

并行,指同一時刻內多任務同時進行; 并發,指在同一時間段內,多任務同時進行著,但是某一時刻,只有某一任務執行;

通常所說的并發連接數,是指瀏覽器向服務器發起請求,建立TCP連接,每秒鐘服務器建立的總連接數,而假如,服務器處10ms能處理一個連接,那么其并發連接數就是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; 循環結束時i==5; JavaScript單線程事件處理器在線程空閑前不會執行下一事件。

如上面第三點所述,如果要真正理解以上例子中的setTimeout(),及JavaScript異步機制,需要理解JavaScript的事件循環和并發模型。

并發模型(Concurrency model)

目前,我們已經知道,JavaScript執行異步任務時,不需要等待響應返回,可以繼續執行其他任務,而在響應返回時,會得到通知,執行回調或事件處理程序。那么這一切具體是如何完成的,又以什么規則或順序運作呢?接下來我們需要解答這個問題。

注:回調和事件處理程序本質上并無區別,只是在不同情況下,不同的叫法。

前文已經提到,JavaScript異步編程使得多個任務可以并發執行,而實現這一功能的基礎是JavScript擁有一個基于事件循環的并發模型。

堆棧與隊列

介紹JavaScript并發模型之前,先簡單介紹堆棧和隊列的區別:

堆(heap):內存中某一未被阻止的區域,通常存儲對象(引用類型); 棧(stack):后進先出的順序存儲數據結構,通常存儲函數參數和基本類型值變量(按值訪問); 隊列(queue):先進先出順序存儲數據結構。 事件循環(Event Loop)

JavaScript引擎負責解析,執行JavaScript代碼,但它并不能單獨運行,通常都得有一個宿主環境,一般如瀏覽器或Node服務器,前文說到的單線程是指在這些宿主環境創建單一線程,提供一種機制,調用JavaScript引擎完成多個JavaScript代碼塊的調度,執行(是的,JavaScript代碼都是按塊執行的),這種機制就稱為事件循環(Event Loop)。

注:這里的事件與DOM事件不要混淆,可以說這里的事件包括DOM事件,所有異步操作都是一個事件,諸如ajax請求就可以看作一個request請求事件。

JavaScript執行環境中存在的兩個結構需要了解:

消息隊列(message queue),也叫任務隊列(task queue):存儲待處理消息及對應的回調函數或事件處理程序; 執行棧(execution context stack),也可以叫執行上下文棧:JavaScript執行棧,顧名思義,是由執行上下文組成,當函數調用時,創建并插入一個執行上下文,通常稱為執行棧幀(frame),存儲著函數參數和局部變量,當該函數執行結束時,彈出該執行棧幀;

注:關于全局代碼,由于所有的代碼都是在全局上下文執行,所以執行棧頂總是全局上下文就很容易理解,直到所有代碼執行完畢,全局上下文退出執行棧,棧清空了;也即是全局上下文是第一個入棧,最后一個出棧。

任務

分析事件循環流程前,先闡述兩個概念,有助于理解事件循環:同步任務和異步任務。

任務很好理解,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異步編程

如上圖,當執行棧同步代碼塊依次執行完直到遇見異步任務時,異步任務進入等待狀態,通知線程,異步事件觸發時,往消息隊列插入一條事件消息;而當執行棧后續同步代碼執行完后,讀取消息隊列,得到一條消息,然后將該消息對應的異步任務入棧,執行回調函數;一次事件循環就完成了,也即處理了一個異步任務。

再談setTimeout(…0)

了解了JavaScript事件循環后我們再看前文關于 setTimeout(...0) 的例子就比較清晰了:

setTimeout(...0) 所表達的意思是:等待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); }

執行多次輸出如下:

JavaScript異步編程

在 setTimeout 異步回調函數里我們輸出了異步任務注冊到執行的時間,發現并不等于我們指定的時間,而且兩次時間間隔也都不同,考慮以下兩點:

在讀取消息隊列的消息時,得等同步任務完成,這個是需要耗費時間的; 消息隊列先進先出原則,讀取此異步事件消息之前,可能還存在其他消息,執行也需要耗時;

所以異步執行時間不精確是必然的,所以我們有必要明白無論是同步任務還是異步任務,都不應該耗時太長,當一個消息耗時太長時,應該盡可能的將其分割成多個消息。

Web Workers

每個Web Worker或一個跨域的iframe都有各自的堆棧和消息隊列,這些不同的文檔只能通過postMessage方法進行通信,當一方監聽了message事件后,另一方才能通過該方法向其發送消息,這個message事件也是異步的,當一方接收到另一方通過postMessage方法發送來的消息后,會向自己的消息隊列插入一條消息,而后續的并發流程依然如上文所述。

JavaScript異步實現

關于JavaScript的異步實現,以前有:回調函數,發布訂閱模式,Promise三類,而在ES6中提出了生成器(Generator)方式實現,關于回調函數和發布訂閱模式實現可參見另一篇文章,后續將推出一篇詳細介紹Promise和Generator。

參考:

Concurrency model and Event Loop

來自:http://blog.codingplayboy.com/2017/04/25/js_async/

標簽: JavaScript
相關文章:
主站蜘蛛池模板: 中文字幕一区二区三区在线视频 | 久久里面有精品 | h视频在线观看免费 | 激情毛片| 91精品国产91久久久久久不卞 | 欧美在线视频免费 | 精久久久 | 亚洲精品久久久久久久久久久久久 | 亚洲福利av | 一区二区三区免费 | a毛片视频网站 | 日韩乱码一二三 | 伊人久久精品一区二区三区 | 久久久久1 | 高清一区二区三区 | 亚洲综合色站 | 精品福利一区二区三区 | 九九在线视频 | 成人黄色在线视频 | 综合九九 | 国产精品久久久久久久久久久久冷 | 亚洲精品视频导航 | 久久精品一 | 久久伊人精品 | 午夜影晥| 一区二区久久精品 | 在线观看国产精品视频 | 国产精品视频网 | av在线播放免费 | 国产精品免费在线 | 男人天堂色 | yiren22 亚洲综合 | 亚洲精久 | 欧美精品一二区 | 色资源在线| 亚洲精品电影 | 天天操夜夜操 | 国产日韩久久 | 国产精品一区二区av | 国产一区视频在线 | 91麻豆精品国产91久久久久久久久 |