你好,JavaScript非同步程式設計---- 理解JavaScript非同步的美妙

酸楚與甘甜發表於2018-07-29

潛心修煉一段時間的我又回來了

每天不能不寫業務,但也不能只寫業務。所以選擇了一個自己還在學校期間沒有學明白的內容進行了鞏固,同步非同步問題。學習一個知識之前必先給自己一個問題三連。為什麼要有這個? 這個怎麼用? 這個怎麼回事?

為什麼要有同步非同步?

首先JS是一個單執行緒的語言。單執行緒的含義類似於從頭走到尾,誰也別管誰,前面堵車我就停(官方:單執行緒在程式執行時,所走的程式路徑按照連續順序排下來,前面的必須處理好,後面的才會執行。),沒辦法開多個執行緒。

疑問來了?為什麼JS不設計成多執行緒的可以開多個執行緒同時操作。JS是可以去操作DOM的。假設JS設計成一個多執行緒語言。你的主執行緒在給DOM的innerHtml做一個賦值操作,你的另一個執行緒把這個DOM結構刪除了。。。。這肯定不可以。(多執行緒可以互不干預的操作一段記憶體空間)。所以乾脆設計成一個單執行緒,哪怕後期HTML5出現的web worker也是不允許操作DOM結構的,可以完成一些分散式的計算。對於dom結構我們必須順序操縱,堅決不允許出現對同一個dom同時進行操作。

但是瀏覽器載入一些需要網路請求的比如圖片資源、ajax。或者輪訓的內容。由於是單執行緒,需要等待這些內容訪問完才可以執行下面的程式碼。那麼你發個ajax請求或者請求個圖片資源,那麼這段時間就什麼也做不了,這種效果對程式是一種阻塞,在等待時間明明可以做些別的事情卻選擇了無意義的等待。(同步阻塞)這個時候非同步就出現了,在涉及需要等待的操作,我們選擇讓程式繼續執行,在等待時間結束的時候,通知一下我們的程式內容執行完畢,你可以操作這些資源了,這段等待時間並不影響你程式的繼續執行,只是在未來的某個時間段(不確定),有一個操作一定會執行,這就是非同步。(非同步非阻塞),這就是為什麼要有同步非同步。

基礎的內容我們講解完畢,下面開始真正的乾貨

(第一次瞭解這個的,請先補習一下定時器與promise的知識)

xxxxxxxxx(一堆不確定的程式碼)

setTimeout(()=>{console.log(111)},500);
複製程式碼

請問列印出111會在什麼時候? 答案是500ms的時候列印

錯!答案是500ms或者500ms以後的某個時間段。

首先遇見定時器後,會將定時器內的函式進行註冊,也就是放入Event Table 。然後在500ms後將Event Table內註冊的函式放入 Event queue。若主執行緒(我也就一個執行緒)中的call stack(呼叫堆疊,也就是執行緒中函式的一個呼叫棧)為空就將Event queue按順序的放入call stack中進行執行。如果call stack並不為空, Event queue內的事件並不會進入call stack中,也就不會執行。

怎麼突然來了這麼多亂七八糟,畫風和剛才不一樣呀。難度上來了。

首先對於非同步事件,我們在執行到這行程式碼的時候會進行一個註冊,將你要在未來某個時間段要執行的函式註冊一下,放在Event table中。這個Event table中可以有很多事件,比如你一次發了好多ajax請求,那麼他們就全部註冊了。在未來的時間到了,就會把註冊的事件放入Event queue(任務佇列)這個任務佇列就是馬上要執行的內容。

任務佇列什麼時候可以執行?在主執行緒的call stack為空的時候,會把任務佇列的第一個事件放入call stack中執行,這裡面涉及一個queue(佇列)的特點就是先進先出。在註冊後先放入Event queue的事件就會更早的離開Event queue進入主執行緒執行。這個時候是不是覺得自己明白點了? 唉別高興的太早了。

來吧 聊一聊巨集任務與微任務吧

setTimeout(()=>{console.log(111)},0)

let promise = new Promise((resolve,reject)=>{
	console.log(222);
	resolve(3333)
});

let promise2 = new Promise((resolve,reject)=>{
	console.log(555);
	resolve(6666)
});

setTimeout(()=>{
	console.log(4444);
},0)

promise.then(res=>{
	console.log(res);
});

promise2.then(res=>{
	console.log(res);
});
複製程式碼

你說這會列印出個什麼? 按理論先進先出,222 555 111 4444 333 666 你看對不對。 事實證明:222 555 333 666 111 4444 這是為什麼? 首先註冊的事件也有不同形態,巨集任務與微任務。

常見的巨集任務:setTimeout、setInterval(定時器類)

常見的微任務:promise process.nextTick(一個node環境的方法)。

這兩個任務的執行規則是什麼?首先在call stack中的內容執行完畢清空後,會在Event queue檢查一下哪些是巨集任務哪些是微任務,然後執行所有的微任務,然後執行一個巨集任務,之後再次執行所有的微任務。也就是說在主執行緒任務執行完畢後會把任務佇列中的微任務全部執行,然後再執行一個巨集任務,這個巨集任務執行完再次檢查佇列內部的微任務,有就全部執行沒有就再執行一個巨集任務。

為什麼我一行獨立的非同步的程式碼也寫不出來

在這個概念理解清楚之後,我大約明白一些了,可是我就是一行真正的脫離了定時器、事件、ajax的非同步的程式碼也寫不出來,我一直在想如何實現訊息通知非同步的訊息通知?觀察者模式嗎?那也沒辦法模擬出來,10分鐘之後我要做一個任務。想了很久。 在有一天需求評審中,我們要做一個定時提醒的需求,這個時候後臺的任務分配中選擇了幾天來製作定時任務。這個時候我理解了。java的定時提醒是要開一個新的執行緒去不斷輪巡時間,可以設定的間隔但是間隔越小,越消耗機器的效能。後來查閱資料瞭解,JS是單執行緒,但是瀏覽器不是,只是執行JS程式碼的引擎是個單執行緒的所以JS的程式碼沒辦法開啟多個執行緒,但是瀏覽器還有定時器執行緒、事件觸發執行緒、非同步http請求執行緒、GUI執行緒。

所以在執行定時器、事件、ajax這些非同步事件的時候是另外三個執行緒在執行程式碼,並不是JS引擎在做事情,在這些執行緒達到某一特定事件把任務放入JS引擎的執行緒中,同時GUI執行緒(渲染介面HTMl的執行緒)與JS執行緒是互斥的,在JS引擎執行時GUI執行緒會被凍結、掛起。

最後最後 JS是單執行緒但是瀏覽器是多執行緒。你的非同步任務是瀏覽器開啟對應的執行緒來執行的,最後放入JS引擎中進行執行。

未完待續

今天基本理解了JS的非同步任務是什麼意思。下一節(可能是下週或者大下週) 我就要針對於Promise 與async/await 以及回撥地獄來進行梳理與學習了。下次還是這個主題我們不見不散。

內容如有不當之處,歡迎各位大佬,評論區指點~~~

最後打個廣告(我們一起維護的學習公眾號)

公眾號主要面向的是初級/應屆生。內容包含我們從應屆生轉換為職場開發所踩過的坑,以及我們每週的學習計劃和學習總結。 內容會涉及計算機網路演算法等基礎;也會涉及前端,後臺,Android等內容~

求關注,不迷路

我們基友團其他朋友的文章:

Android基友

Java基友

相關文章