最近看了不少關於JS運行機制的文章,每篇都獲益匪淺,但各有不一樣,因此在這裏對這幾篇文章裏說的很精闢的地方作一個總結,參考文章連接見最後。本文博客地址
進程
是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)線程
是cpu調度的最小單位(線程是創建在進程的基礎上的一次程序運行單位,一個進程中能夠有多個線程)進程
之間也能夠通訊,不過代價較大單線程
與多線程
,都是指在一個進程
內的單和多詳情看我上篇總結瀏覽器執行機制的文章- 深刻前端-完全搞懂瀏覽器運行機制
既然js是單線程,那麼問題來了,某一些很是耗時間的任務就會致使阻塞,難道必須等前面的任務一步一步執行玩嗎?
好比我再排隊就餐,前面很長的隊列,我一直在那裏等豈不是很傻逼,說以就會有排號系統產生,咱們訂餐後給咱們一個號碼,叫到號碼直接去就好了,沒交咱們以前咱們能夠去幹其餘的事情。
所以聰明的程序員將任務分爲兩類:javascript
能夠理解是每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)
每個task會從頭至尾將這個任務執行完畢,不會執行其它
瀏覽器爲了可以使得JS內部task與DOM任務可以有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行從新渲染
(task->渲染->task->...)html
也就是說,在當前task任務後,下一個task以前,在渲染以前
因此它的響應速度相比setTimeout(setTimeout是task)會更快,由於無需等渲染
也就是說,在某一個macrotask執行完後,就會將在它執行期間產生的全部microtask都執行完畢(在渲染前)前端
主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各類外部API,它們在"任務隊列"中加入各類事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。 java
那怎麼知道主線程執行棧爲執行完畢?js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去Event Queue那裏檢查是否有等待被調用的函數。程序員
第一輪事件循環:
主線程執行js整段代碼(宏任務),將ajax、setTimeout、promise等回調函數註冊到Event Queue,並區分宏任務和微任務。
主線程提取並執行Event Queue 中的ajax、promise等全部微任務,並註冊微任務中的異步任務到Event Queue。
第二輪事件循環:
主線程提取Event Queue 中的第一個宏任務(一般是setTimeout)。
主線程執行setTimeout宏任務,並註冊setTimeout代碼中的異步任務到Event Queue(若是有)。
執行Event Queue中的全部微任務,並註冊微任務中的異步任務到Event Queue(若是有)。
相似的循環:宏任務每執行完一個,就清空一次事件隊列中的微任務。ajax
注意:事件隊列中分「宏任務隊列」和「微任務隊列」,每執行一次任務均可能註冊新的宏任務或微任務到相應的任務隊列中,只要遵循「每執行一個宏任務,就會清空一次事件隊列中的全部微任務」這一循環規則,就不會弄亂。promise
let data = []; $.ajax({ url:www.javascript.com, data:data, success:() => { console.log('發送成功!'); } }) console.log('代碼執行結束');
1.執行整個代碼,遇到ajax異步操做
2.ajax進入Event Table,註冊回調函數success。
3.執行console.log('代碼執行結束')。
4.執行ajax異步操做
5.ajax事件完成,回調函數success進入Event Queue。
5.主線程從Event Queue讀取回調函數success並執行。瀏覽器
new Promise((r) => { r(); }) .then(() => console.log(1)) .then(() => console.log(2)) .then(() => console.log(3)) new Promise((r) => { r(); }) .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6))
執行的結果是 1 4 2 5 3 6數據結構
then()
鏈式調用,並非連續的建立了多個微任務並推入微任務隊列,由於then()
的返回值必然是一個 Promise,然後續的then()
是上一步then()
返回的 Promise 的回調resolve()
,將 Promise 的狀態改變爲<resolved>: undefined
, 而後 then 中傳入的回調函數console.log('1')
做爲一個微任務被推入微任務隊列then()
中傳入的回調函數console.log('2')
此時尚未被推入微任務隊列,只有上一個then()
中的console.log('1')
執行完畢後,console.log('2')
纔會被推入微任務隊列setTimeout(function(){ console.log('定時器開始啦') }); new Promise(function(resolve){ console.log('立刻執行for循環啦'); for(var i = 0; i < 10000; i++){ i == 99 && resolve(); } }).then(function(){ console.log('執行then函數啦') }); console.log('代碼執行結束');
1.整段代碼做爲宏任務執行,遇到setTimeout宏任務分配到宏任務Event Queue中
2.遇到promise內部爲同步方法直接執行-「立刻執行for循環啦」
3.註冊then回調到Eventqueen
4.主代碼宏任務執行完畢-「代碼執行結束」
5.主代碼宏任務結束被monitoring process進程監聽到,主任務執行Event Queue的微任務
6.微任務執行完畢-「執行then函數啦」
7.執行宏任務console.log('定時器開始啦')多線程
// 1 2 6 4 3 5 //console.log(3)實際上是在async2函數返回的Promise的then語句中執行的 async function async1() { console.log(1); const result = await async2(); console.log(3); } async function async2() { console.log(2); } //console.log(async2()) Promise.resolve().then(() => { console.log(4); }); setTimeout(() => { console.log(5); }); async1(); console.log(6);
console.log('1'); // 1 6 7 2 4 5 9 10 11 8 3 // 記做 set1 setTimeout(function () { console.log('2'); // set4 setTimeout(function() { console.log('3'); }); // pro2 new Promise(function (resolve) { console.log('4'); resolve(); }).then(function () { console.log('5') }) }) // 記做 pro1 new Promise(function (resolve) { console.log('6'); resolve(); }).then(function () { console.log('7'); // set3 setTimeout(function() { console.log('8'); }); }) // 記做 set2 setTimeout(function () { console.log('9'); // 記做 pro3 new Promise(function (resolve) { console.log('10'); resolve(); }).then(function () { console.log('11'); }) })
1.總體script做爲第一個宏任務進入主線程,遇到console.log,輸出1。
2.遇到set1,其回調函數被分發到宏任務Event Queue中。
3.遇到pro1,new Promise直接執行,輸出6。then被分發到微任務Event Queue中。
4.遇到了set2,其回調函數被分發到宏任務Event Queue中。
1.主線程執行隊列中第一個宏任務set1,輸出2;代碼中遇到了set4,註冊回調;又遇到了pro2,new promise()直接執行輸出4,並註冊回調;
2.set1宏任務執行完畢,開始清空微任務,主線程執行微任務pro2,輸出5。
1.主線程執行隊列中第一個宏任務set2,輸出9;代碼中遇到了pro3,new promise()直接輸出10,並註冊回調;
2.set2宏任務執行完畢,開始狀況微任務,主線程執行微任務pro3,輸出11。
相似循環...
因此最後輸出結果爲一、六、七、二、四、五、九、十、十一、八、3。