徹底弄懂 JavaScript 執行機制
本文的目的就是要保證你徹底弄懂javascript的執行機制,如果讀完本文還不懂,可以揍我
1、關於javascript
javascript是一門單執行緒語言,在最新的HTML5中提出了Web-Worker,但javascript是單執行緒這一核心仍未改變。所以一切javascript版的"多執行緒"都是用單執行緒模擬出來的,一切javascript多執行緒都是紙老虎
2、概念
1. 巨集任務:當前呼叫棧中執行的程式碼成為巨集任務。(主程式碼塊,定時器等等)。
2. 微任務: 當前(此次事件迴圈中)巨集任務執行完,在下一個巨集任務開始之前需要執行的任務,可以理解為回撥事件。(promise.then,proness.nextTick等等)。 3. 巨集任務中的事件放在callback queue中,由事件觸發執行緒維護;微任務的事件放在微任務佇列中,由js引擎執行緒維護。
通俗易懂的例子:
去銀行辦理業務的人就是一個個巨集任務,當巨集任務P1在櫃檯辦理業務時,其它任務都需等待,當一個巨集任務P1辦理業務結束時,櫃檯職員會詢問他還有沒有其它微任務,如果他還有其他業務,則其他巨集任務都需等待。就是微任務是在巨集任務之前執行。
微任務:process.nextTick、MutationObserver、Promise.then catch finally
巨集任務:I/O、setTimeout、setInterval、setImmediate、requestAnimationFrame
巨集任務、微任務的執行順序:
執行順序:先執行同步程式碼,遇到非同步巨集任務則將非同步巨集任務放入巨集任務佇列中,遇到非同步微任務則將非同步微任務放入微任務佇列中,當所有同步程式碼執行完畢後,再將非同步微任務從佇列中調入主執行緒執行,微任務執行完畢後再將非同步巨集任務從佇列中調入主執行緒執行,一直迴圈直至所有任務執行完畢。
3、執行機制
1. 在執行棧中執行一個巨集任務。
2. 執行過程中遇到微任務,將微任務新增到微任務佇列中。
3. 當前巨集任務執行完畢,立即執行微任務佇列中的任務。
4. 當前微任務佇列中的任務執行完畢,檢查渲染,GUI執行緒接管渲染。
5. 渲染完畢後,js執行緒接管,開啟下一次事件迴圈,執行下一次巨集任務(事件佇列中取)。
例項剖析:1
setTimeout(function(){ console.log('1'); }); new Promise(function(resolve){ console.log('2'); resolve(); }).then(function(){ console.log('3'); }); console.log('4');
- 遇到setTimout,非同步巨集任務,放入巨集任務佇列中;
- 遇到new Promise,new Promise在例項化的過程中所執行的程式碼都是同步進行的,所以輸出2;
- 而Promise.then中註冊的回撥才是非同步執行的,將其放入微任務佇列中
- 遇到同步任務console.log(‘4’);輸出4;主執行緒中同步任務執行完
- 從微任務佇列中取出任務到主執行緒中,輸出3,微任務佇列為空
- 從巨集任務佇列中取出任務到主執行緒中,輸出1,巨集任務佇列為空,結束~
控制檯測試一下,輸出2 4 3 1;符合預期
例項剖析:2
setTimeout(()=>{ new Promise(resolve =>{ resolve(); }).then(()=>{ console.log('test'); }); console.log(4); }); new Promise(resolve => { resolve(); console.log(1) }).then( () => { console.log(3); Promise.resolve().then(() => { console.log('before timeout'); }).then(() => { Promise.resolve().then(() => { console.log('also before timeout') }) }) }) console.log(2);
- 遇到setTimeout,非同步巨集任務,將() => {console.log(4)}放入巨集任務佇列中;
- 遇到new Promise,new Promise在例項化的過程中所執行的程式碼都是同步進行的,所以輸出1;
- 而Promise.then中註冊的回撥才是非同步執行的,將其放入微任務佇列中
- 遇到同步任務console.log(2),輸出2;主執行緒中同步任務執行完
- 從微任務佇列中取出任務到主執行緒中,輸出3,此微任務中又有微任務,Promise.resolve().then(微任務a).then(微任務b),將其依次放入微任務佇列中;
- 從微任務佇列中取出任務a到主執行緒中,輸出 before timeout;
- 從微任務佇列中取出任務b到主執行緒中,任務b又註冊了一個微任務c,放入微任務佇列中;
- 從微任務佇列中取出任務c到主執行緒中,輸出 also before timeout;微任務佇列為空
- 從巨集任務佇列中取出任務到主執行緒,此任務中註冊了一個微任務d,將其放入微任務佇列中,接下來遇到輸出4,巨集任務佇列為空
- 從微任務佇列中取出任務d到主執行緒 ,輸出test,微任務佇列為空,結束~
控制檯測試輸出:1 2 3 before timeout also before timeout 4 test
例項剖析:3
console.log('1'); setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) })
最後嘗試著做一個比複雜的吧
這是我的分析過程
完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12