在理解事件循環以前咱們先了解一下瀏覽器進程和線程二三事javascript
進程:cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)
線程:進程中執行的每個任務指的就是線程,系統不會爲其分配內存資源,各個線程共享進程擁有的內存資源。cpu調度的最小單位(線程是創建在進程的基礎上的一次程序運行單位)java
一、一個程序至少有一個進程,一個進程至少有一個線程
二、線程不能脫離進程而獨立運行
三、線程沒有地址空間,線程包含在進程的地址空間中。線程上下文只包含一個堆棧、一個寄存器、一個優先權
四、進程內的任何線程都被看作是同位體,且處於相同的級別
五、進程中任何線程均可以經過銷燬主線程來銷燬進程,銷燬主線程將致使該進程的銷燬,對主線程的修改可能影響全部的線程。編程
瀏覽器是多線程的,通常來講每打開一個標籤頁就算是建立了一個獨立的進程
除了標籤進程完,瀏覽器進程還有:數組
負責瀏覽器界面顯示,與用戶交互,如前進、後退等
負責各個頁面的管理,建立和銷燬其餘進程
將Renderer進程獲得的內存中的Bitmap,繪製到用戶界面上
網絡資源的管理,下載等promise
用於硬件加速圖形繪製瀏覽器
用於解析頁面,渲染頁面,執行腳本,處理事件等等網絡
每種類型的插件對應一個進程,僅當使用該插件時才建立數據結構
裏面有多個線程,靠着這些現成共同完成渲染任務多線程
負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,佈局和繪製等
當界面須要重繪(Repaint)或因爲某種操做引起迴流(reflow)時異步
負責處理Javascript腳本程序。(例如V8引擎)
瀏覽器不管何時都只有一個JS引擎在運行JS程序
歸屬於渲染(瀏覽器內核)進程,不受JS引擎線程控制
用於控制事件(例如鼠標,鍵盤等事件),當該事件被觸發時候,事件觸發線程就會把該事件的處理函數添加進任務隊列中,等待JS引擎線程空閒後執行
setInterval與setTimeout所在線程
瀏覽器定時計數器並非由JavaScript引擎計數的,(由於JavaScript引擎是單線程的, 若是處於阻塞線程狀態就會影響記計時的準確)
所以經過單獨線程來計時並觸發定時(計時完畢後,添加到事件隊列中,等待JS引擎空閒後執行)
注意:W3C的HTML標準中規定,setTimeout中低與4ms的時間間隔算爲4ms
在XMLHttpRequest在鏈接後是經過瀏覽器新開一個線程請求
將檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件,將這個回調再放入事件隊列中。再由JavaScript引擎執行。
總結:須要知道的是瀏覽器中js線程只有一個,若是發起一個異步IO請求,在等待響應的這段時間後面的代碼會被阻塞。因爲js主線程和GUI渲染線程是相互阻塞的,因此就形成了瀏覽器假死。
事件循環本質是用來作調度的,決定什麼時候將資源分配給誰
V8只是負責Js代碼的解析和執行,而事件循環決定了V8何時執行什麼代碼。
對象、數組被存放在堆中
JavaScript 是一門單線程的語言,這意味着它只有一個調用棧,所以,它同一時間只能作一件事。
每調用一個函數,解釋器就會把該函數添加進調用棧並開始執行
正在調用棧中執行的函數還調用了其它函數,那麼新函數也將會被添加進調用棧,一旦這個函數被調用,便會當即執行。
當前函數執行完畢後,解釋器將其從棧頂移出調用棧,繼續執行當前執行環境下的剩餘的代碼
當分配的調用棧空間被佔滿時,會引起「堆棧溢出」。
javascript是單線程。單線程就意味着,全部任務須要排隊
全部的任務能夠分爲同步任務和異步任務
同步任務通常會直接進入到主線程中執行
異步任務不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。
異步任務運行機制:
一、全部同步任務都在主線程上執行,造成一個執行棧
二、主線程以外,還存在一個"任務隊列"。只要異步操做執行完成,就到任務隊列中排隊
三、一旦執行棧中的全部同步任務執行完畢,系統就會按次序讀取任務隊列中的異步任務,因而被讀取的異步任務結束等待狀態,進入執行棧,開始執行
四、主線程不斷重複上面的第三步。
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件循環)
在事件循環中,每進行一次循環操做稱爲tick
分爲宏任務(macrotask )和爲任務(microtask )
而且每一個宏任務結束後, 都要清空全部的微任務,這裏的 Macro Task也是咱們常說的 task ,
包括:script( 總體代碼)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 環境)
包括:Promise、MutaionObserver、process.nextTick(Node.js 環境)
執行順序:
JS 引擎會將全部任務按照類別分到這兩個隊列中
一、在 宏任務 的隊列中取出第一個任務(通常最初始,宏任務隊列中,只有一個 scrip t(總體代碼)任務)
二、執行完畢後取出 微任務 隊列中的全部任務順序執行
三、再取 宏任務,周而復始,直至兩個隊列的任務都取完
tip: 因爲microtask 優先於 task 執行,因此若是有須要優先執行的邏輯,放入microtask 隊列會比 task 更早的被執行
優勢是簡單、容易理解和部署
缺點是容易產生回調地獄
採用事件驅動模式
任務的執行不取決於代碼的順序,而取決於某個事件是否發生。
function f1(){ setTimeout(function () { // f1的任務代碼 f1.trigger('done'); // 執行完成後,當即觸發done事件,從而開始執行f2 }, 1000); } f1.on('done', f2); // 當f1發生done事件,就執行f2
優勢:能夠綁定多個事件,每一個事件能夠指定多個回調函數,並且能夠"去耦合"(Decoupling),有利於實現模塊化
缺點: 整個程序都要變成事件驅動型,運行流程會變得很不清晰。
jQuery.subscribe("done", f2); function f1(){ setTimeout(function () { // f1的任務代碼 jQuery.publish("done") ; // f1執行完成後,向"信號中心"jQuery發佈"done"信號,從而引起f2的執行。 }, 1000); } jQuery.unsubscribe("done", f2); // f2完成執行後,也能夠取消訂閱(unsubscribe)
對象的狀態不受外界影響
一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果
Promise對象的狀態改變,只有兩種可能:從Pending變爲Resolved和從Pending變爲Rejected。
優勢:將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操做更加容易
缺點:
一、沒法取消Promise,一旦新建它就會當即執行,沒法中途取消;
二、若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部
三、當處於Pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)
Generator函數返回的遍歷器對象,yield語句暫停,調用next方法恢復執行,若是沒遇到新的yeild,一直運行到return語句爲止,return 後面表達式的值做爲返回對象的value值,若是沒有return語句,一直運行到結束,返回對象的value爲undefined。
async函數就是Generator函數的語法糖。
async函數返回一個 Promise對象,可使用then方法添加回調函數
關鍵字await使async函數一直等待(執行棧固然不可能停下來等待的,await將其後面的內容包裝成promise交給Web APIs後,執行棧會跳出async函數繼續執行),直到promise執行完並返回結果。
await只在async函數函數裏面奏效。
await關鍵字後面的函數裏面的同步代碼和主線程同步執行
await語句後面的語句就至關因而await XXX promise resolved後then裏面的東西
async function async1() { console.log('async1 start'); await async2(); console.log('asnyc1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(() => { console.log('setTimeOut'); }, 0); async1(); new Promise(function (reslove) { console.log('promise1'); reslove(); }).then(function () { console.log('promise2'); }) console.log('script end');
執行結果:
script start async1 start async2 promise1 script end asnyc1 end promise2 setTimeOut
理解:
一、new Promise是同步的任務,會被放到主進程中去當即執行。而.then()函數是異步任務會放到異步隊列中去,那何時放到異步隊列中去呢?當你的promise狀態結束的時候,就會當即放進異步隊列中去了
二、帶async關鍵字的函數會返回一個promise對象,若是裏面沒有await,執行起來等同於普通函數,和主線程同步執行
三、若是帶await,此時的await會讓出線程,阻塞async內後續的代碼,先去執行async外的代碼。等外面的同步代碼執行完畢,纔會執行裏面的後續代碼。就算await的不是promise對象,是一個同步函數,也會等這樣操做
注:setTimeOut並非直接的把你的回掉函數放進上述的異步隊列中去,而是在定時器的時間到了以後,把回掉函數放到執行異步隊列中去。若是此時這個隊列已經有不少任務了,那就排在他們的後面。這也就解釋了爲何setTimeOut爲何不能精準的執行的問題了。
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { //async2作出以下更改: new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise3'); resolve(); }).then(function() { console.log('promise4'); }); console.log('script end');
輸出結果:
script start async1 start promise1 promise3 script end promise2 async1 end promise4 setTimeout