閱讀本文,你將知道:html
在最初學習JavaScript的時候,就從各個地方得知JavaScript是一門單線程編程語言,可是使人疑惑的是,爲何一個單線程語言可以同時執行HTTP請求同時渲染頁面?爲何代碼的書寫順序和執行順序並不一致?web
這就是異步帶來的效果ajax
這裏咱們必須達到以下的共識:編程
這裏須要注意的是GUI渲染進程和JavaScript引擎進程是互斥的,由於若是這兩個線程能夠同時運行的話,JavaScript的DOM操做將會擾亂渲染線程執行渲染先後的數據一致性。瀏覽器
本文想要探討的,是JavaScript線程裏的異步設計,千萬別和多線程混淆了。網絡
想要了解更多關於瀏覽器多線程機制請參考:www.cnblogs.com/hksac/p/659…多線程
一切得先從CPU開始講起:異步
CPU的指令執行速度是遠高於硬盤讀取速度和主存讀取速度的。而I/O操做就會涉及到硬盤存取和主存讀取,常見的I/O操做有文件I/O,網絡I/O。(I/O = Input / Output)。編程語言
因此,觀察如下這一段僞代碼:函數
var a = 2;
for(let i =0;i<10;i++){
doSomeWork();
}
let buffer = openFile('./work.txt')
buffer.add('hello world');
複製代碼
在CPU眼中,他會把代碼當作這兩部分:
綠色部分由於不涉及到I/O操做,因此CPU執行速度超快,可是當運行到紅色部分時,倒是一個很是耗時的操做,而這段時間,CPU是處於一個'無所事事'的狀態(DMA獲取總線控制權以後一切I/O與CPU無關),由於文件若是沒有讀取進來,下面的工做也沒法開展。
同步在這裏的意思,即書寫代碼的順序就是代碼執行的順序,若是JavaScript設計成同步的話,那麼當執行到openFile
這一行的時候,將會等待該I/O操做完成CPU才繼續往下執行。
設想一下,當發送Ajax請求(網絡I/O)的時候,整個頁面被阻塞沒法操做將會是多差的體驗。
而諸如鼠標點擊事件,滑動事件,失焦事件,在CPU看來,都是處理得特別慢的事件(雖然對咱們來講是一瞬間的事情),若是將JavaScript設計成同步,也會特別浪費CPU性能。
而阻塞和非阻塞關注的CPU在I/O發生時的工做狀況
在上面這個讀取文件的例子中
若是沒法區分同步
和阻塞
,請參考這裏
異步則解決了代碼被耗時任務阻止其往下執行的缺點
多線程異步有着比較好的解決方案:
JavaScript是一門單線程語言,自己沒法提供多線程,那麼是經過怎樣的機制來實現異步的?
先給出答案:JavaScript經過事件循環和瀏覽器各線程協調共同實現異步
JavaScript認爲任務分爲兩種,一種是全由CPU決定完成速度的任務,咱們稱其爲同步任務,一種是由多種因素(如硬盤讀取速度,網速,點擊反饋速度)決定完成速度的任務,咱們稱其爲異步任務。
舉個簡單的例子
JavaScript將全部的異步任務都會放進一個隊列裏面,在執行完全部的同步任務以後,會去隊列中找到最早進入隊列的異步任務執行。
仔細觀察上圖,結合本文在最開始提到的瀏覽器多線程設計:
由於諸如事件觸發,http請求都是耗時沒法直接肯定的任務,也就是說JavaScript線程沒法得知異步的任務回調函數究竟何時會寫入異步任務隊列,那麼這個地方,就須要一個機制,去時刻輪詢這個任務隊列,這就是事件循環(event loop)
如今咱們再看以下代碼的執行順序:
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function (){};
req.onerror = function (){};
req.send();
複製代碼
真正的執行順序是
var req = new XMLHttpRequest();
req.open('GET', url);
req.send(); // i am here
req.onload = function (){};
req.onerror = function (){};
複製代碼
同理
while(1){
console.log('1')
}
setTimeOut(()=>{
console.log('00000000000000000')
},1)
複製代碼
也將一樣永遠不會輸出00000000000000000
由於JavaScript的工做環境是一個典型的異步應用場景:充斥着各類ajax事件和瀏覽器事件。各個事件的觸發時間和獲得反饋的時間都不得而知,若是設計成同步語言,將會帶來極差的瀏覽器使用體驗。 須要設計一個成一個生產者-消費者
模型(也能夠看作是觀察者模式),來管理這樣的異步任務。
瀏覽器須要作的事情太多了,一手須要負責渲染,一手須要負責http請求,一手還須要執行JavaScript,將JavaScript設計成單線程不只可以讓瀏覽器更好地控制各個線程,同時對開發者來講也更簡單。多線程涉及到鎖,臨界區,衝突解決的學習成本仍是比較高的。
再次來看一下這一句話:
JavaScript是異步事件驅動的單線程編程語言
異步:寫代碼順序不必定是執行順序,JavaScript線程先執行同步任務。 事件驅動:其餘線程在各事件完成後將回調函數寫入隊列,都是以抽象事件做爲觸發機制的。 單線程:不能開多線程而是用eventloop來實現異步的。
JavaScript經過事件循環和瀏覽器各線程協調共同實現異步
JavaScript的異步設計很是優秀,這也讓基於V8引擎的Node在服務端大方異彩,可以更加簡單地開發出適合高密集I/O的web應用。
以後會基於JavaScript的EventLoop總結下關於Node的異步I/O(涉及到多線程)
若是喜歡,請關注
最新博客會最早更新在http://www.helloyzy.cn