宏任務(wù):
當(dāng)前調(diào)用棧執(zhí)行的代碼成為宏任務(wù)诀黍,(主代碼塊和定時器)也或者宿主環(huán)境提供的叫宏任務(wù)
這些任務(wù)包括:
- 渲染事件
- 用戶交互事件(如鼠標(biāo)點擊、滾動頁面、放大縮小等)
- JavaScript 腳本執(zhí)行事件酒唉;
- 網(wǎng)絡(luò)請求完成墙基、文件讀寫完成事件
微任務(wù):
當(dāng)前(此次事件循環(huán)中)宏任務(wù)執(zhí)行完,在下一個宏任務(wù)開始之前需要執(zhí)行的任務(wù),可以理解為回調(diào)事件:promise.then,proness.nextTick等等。 由語言標(biāo)準(zhǔn)提供的叫微任務(wù).
執(zhí)行順序:
在掛起任務(wù)的時候添谊, JS 引擎會把任務(wù)按照類別分到兩個隊伍當(dāng)中财喳。 首先在宏任務(wù)(macrotask) 隊伍中取出第一個任務(wù),執(zhí)行完畢后斩狱。取出 microtask 隊列中的所有任務(wù)順序執(zhí)行耳高;周而復(fù)始,循環(huán)所踊。
總結(jié)上面:
可以舉個例子:就像銀行柜臺辦理業(yè)務(wù)泌枪,每個來辦理業(yè)務(wù)的人就像一個一個宏任務(wù),當(dāng)前用戶業(yè)務(wù)辦理完成然后 接待下一個客戶秕岛,就像開始了下一個宏任務(wù)一樣碌燕。但是一個客戶可能要辦理多項業(yè)務(wù)(修改密碼,存款继薛,轉(zhuǎn)賬等)這些業(yè)務(wù)就像微任務(wù)修壕,只有微任務(wù)執(zhí)行完成,才能執(zhí)行下一個宏任務(wù)(總不能一個客戶業(yè)務(wù)沒有辦理完惋增,就讓他去重新取號排隊叠殷,估計要打人了!U┟蟆A质)
setTimeout(function(){
console.log('定時器開始啦')
});
new Promise(function(resolve){
console.log('馬上執(zhí)行for循環(huán)啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('執(zhí)行then函數(shù)啦')
});
console.log('代碼執(zhí)行結(jié)束');
//馬上執(zhí)行for循環(huán)啦
//代碼執(zhí)行結(jié)束
//執(zhí)行then函數(shù)啦
//定時器開始啦
- 這段代碼作為宏任務(wù)像棘,進(jìn)入主線程。
- 先遇到setTimeout壶冒,那么將其回調(diào)函數(shù)注冊后分發(fā)到宏任務(wù)Event Queue缕题。(注冊過程與上同,下文不再描述)
- 接下來遇到了Promise胖腾,new Promise立即執(zhí)行烟零,then函數(shù)分發(fā)到微任務(wù)Event Queue。
- 遇到console.log()咸作,立即執(zhí)行锨阿。
- 好啦,整體代碼script作為第一個宏任務(wù)執(zhí)行結(jié)束记罚,看看有哪些微任務(wù)墅诡?我們發(fā)現(xiàn)了then在微任務(wù)Event Queue里面,執(zhí)行桐智。
- ok末早,第一輪事件循環(huán)結(jié)束了,我們開始第二輪循環(huán)说庭,當(dāng)然要從宏任務(wù)Event Queue開始然磷。我們發(fā)現(xiàn)了宏任務(wù)Event Queue中setTimeout對應(yīng)的回調(diào)函數(shù),立即執(zhí)行刊驴。
- 結(jié)束
看下demo:
Promise 在前 setTimeout在后面
new Promise((resolve) => {
console.log('外層宏事件2');
resolve()
}).then(() => {
console.log('微事件1');
}).then(()=>{
console.log('微事件2')
})
console.log('外層宏事件1');
setTimeout(() => {
//執(zhí)行后 回調(diào)一個宏事件
console.log('內(nèi)層宏事件3')
}, 0)
// 執(zhí)行結(jié)果:
外層宏事件2
外層宏事件1
微事件1
微事件2
內(nèi)層宏事件3
- Promise 在前姿搜,Promise.then則是具有代表性的微任務(wù),所有會進(jìn)入的異步都是指事件的回調(diào)捆憎。所以說 new Promise 在實例化的過程中所執(zhí)行的代碼都是同步進(jìn)行的痪欲,而then 中的才是異步執(zhí)行的
- setTimeout就是作為宏任務(wù)來存在的
- 在同步執(zhí)行完成之后,檢查是否有異步任務(wù)攻礼,微任務(wù)會在下一個宏任務(wù)前面全部完成
- 所以 結(jié)果是 外2-外1-微1-微2-內(nèi)3
setTimeout 在前面 Promise 在后面
setTimeout(() => {
//執(zhí)行后 回調(diào)一個宏事件
console.log('內(nèi)層宏事件3')
}, 0)
console.log('外層宏事件1');
new Promise((resolve) => {
console.log('外層宏事件2');
resolve()
}).then(() => {
console.log('微事件1');
}).then(()=>{
console.log('微事件2')
})
// 執(zhí)行結(jié)果:
外層宏事件1
外層宏事件2
微事件1
微事件2
內(nèi)層宏事件3
- setTimeout 設(shè)定了時間,相當(dāng)于取號了栗柒,在排隊過程礁扮。
- 然后在當(dāng)前進(jìn)程中又添加了一些Promise的處理(臨時添加的業(yè)務(wù))
- 同步的外1 和外2 執(zhí)行完成,開始執(zhí)行微任務(wù)瞬沦,微任務(wù)執(zhí)行完成之后才執(zhí)行下一個異步宏任務(wù)太伊,所以結(jié)果如上;
前面提到宿主環(huán)境:能夠使js 完美運行的環(huán)境逛钻,目前常見的環(huán)境就是兩種宿主環(huán)境有瀏覽器和node
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
- 整體script作為第一個宏任務(wù)進(jìn)入主線程僚焦,遇到console.log,輸出1曙痘。
- 遇到setTimeout芳悲,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中立肘。我們暫且記為setTimeout1。
- 遇到process.nextTick()名扛,其回調(diào)函數(shù)被分發(fā)到微任務(wù)Event Queue中谅年。我們記為process1。
- 遇到Promise肮韧,new Promise直接執(zhí)行融蹂,輸出7。then被分發(fā)到微任務(wù)Event Queue中弄企。我們記為then1超燃。
- 又遇到了setTimeout,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中拘领,我們記為setTimeout2意乓。
- 同步輸出1-7 執(zhí)行微任務(wù) process1-then1 輸出6 - 8
- 好了,第一輪事件循環(huán)正式結(jié)束院究,這一輪的結(jié)果是輸出1洽瞬,7,6业汰,8伙窃。那么第二輪時間循環(huán)從setTimeout1宏任務(wù)開始:
- 首先輸出2。接下來遇到了process.nextTick()样漆,同樣將其分發(fā)到微任務(wù)Event Queue中为障,記為process2。new Promise立即執(zhí)行輸出4放祟,then也分發(fā)到微任務(wù)Event Queue中鳍怨,記為then2。
- 第二輪事件循環(huán)宏任務(wù)結(jié)束跪妥,我們發(fā)現(xiàn)有process2和then2兩個微任務(wù)可以執(zhí)行鞋喇。 輸出 3和5
- 第二輪事件循環(huán)結(jié)束,第二輪輸出2眉撵,4侦香,3,5纽疟。
- 第三輪事件循環(huán)開始罐韩,此時只剩setTimeout2了,執(zhí)行污朽。
- 輸出9散吵,11,10,12矾睦。
node 環(huán)境和瀏覽器環(huán)境 又有什么區(qū)別呢晦款?
-
宏任務(wù)
requestAnimationFrame
姑且也算是宏任務(wù)吧,requestAnimationFrame
在MDN的定義為顷锰,下次頁面重繪前所執(zhí)行的操作柬赐,而重繪也是作為宏任務(wù)的一個步驟來存在的,且該步驟晚于微任務(wù)的執(zhí)行微任務(wù)
JS 是單線程官紫,所以同一個時間不能處理多個任務(wù)肛宋,所以每次辦理完一個業(yè)務(wù),都會詢問當(dāng)前客戶是否還有其他要辦理的業(yè)務(wù)(檢查是否有未完成的微任務(wù))束世,當(dāng)前用戶辦理完成酝陈,結(jié)束這個宏任務(wù)開始下一個宏任務(wù),這樣操作持續(xù)進(jìn)行毁涉,而這樣的操作就被稱為Event Loop
什么是Event Loop沉帮?
event loop 顧名思義 就是事件循環(huán)。因為V8是單線程的贫堰,即同一時間只能干一件事情穆壕,但是呢文件的讀取,網(wǎng)絡(luò)的IO處理是很緩慢的其屏,并且是不確定的,如果同步等待它們響應(yīng)喇勋,那么用戶就起飛了。于是我們就把這個事件加入到一個 事件隊列里(task),等到事件完成時偎行,event loop再執(zhí)行另一個事件隊列川背。
1、 update_time
在事件循環(huán)的開頭蛤袒,這一步的作用實際上是為了獲取一下系統(tǒng)時間
2熄云、timers
事件循環(huán)跑到這個階段的時候,要檢查是否有到期的timer,其實也就是setTimeout和setInterval這種類型的timer妙真,到期了缴允,就會執(zhí)行他們的回調(diào)。
3珍德、I/O callbacks
處理異步事件的回調(diào)癌椿,比如網(wǎng)絡(luò)I/O,比如文件讀取I/O菱阵。當(dāng)這些I/O動作都結(jié)束的時候,在這個階段會觸發(fā)它們的回調(diào)缩功。
4晴及、idle, prepare
這個階段內(nèi)部做一些動作,與理解事件循環(huán)沒啥關(guān)系
5嫡锌、I/O poll階段
這個階段相當(dāng)有意思虑稼,也是事件循環(huán)設(shè)計的一個有趣的點琳钉。這個階段是選擇運行的。選擇運行的意思就是不一定會運行蛛倦。
6歌懒、check
執(zhí)行setImmediate操作
7、close callbacks
關(guān)閉I/O的動作溯壶,比如文件描述符的關(guān)閉及皂,鏈接斷開
除了task還有一個microtask,這一個概念是ES6提出Promise以后出現(xiàn)的且改。這個microtask queue只有一個验烧。
并且會在且一定會在每一個task后執(zhí)行,且執(zhí)行是按順序的又跛。加入到microtask 的事件類型有Promise.resolve().then(), process.nextTick() 值得注意的是
event loop一定會在執(zhí)行完micrtask以后才會尋找新的 可執(zhí)行的task隊列碍拆。而microtask事件內(nèi)部又可以產(chǎn)生新的microtask
瀏覽器:
- 宏任務(wù)(macroTask):script 中代碼、setTimeout慨蓝、setInterval感混、I/O、UI render
- 微任務(wù)(microTask): Promise礼烈、Object.observe弧满、MutationObserver。
- I/O 有點籠統(tǒng)济丘,點擊個btn 上傳一個文件谱秽,與程序交互的這些都可以稱為I/O
<style>
#outer {
padding: 20px;
background: #616161;
}
#inner {
width: 100px;
height: 100px;
background: #757575;
}
</style>
<div id="outer">
<div id="inner"></div>
</div>
const $inner = document.querySelector('#inner')
const $outer = document.querySelector('#outer')
function handler () {
console.log('click') // 直接輸出
Promise.resolve().then(_ => console.log('promise')) // 注冊微任務(wù)
setTimeout(_ => console.log('timeout')) // 注冊宏任務(wù)
requestAnimationFrame(_ => console.log('animationFrame')) // 注冊宏任務(wù)
$outer.setAttribute('data-random', Math.random()) // DOM屬性修改,觸發(fā)微任務(wù)
}
new MutationObserver(_ => {
console.log('observer')
}).observe($outer, {
attributes: true
})
$inner.addEventListener('click', handler)
$outer.addEventListener('click', handler)
1摹迷、因為一次I/O創(chuàng)建了一個宏任務(wù)疟赊,也就是說在這次任務(wù)中會去觸發(fā)handler
2、在同步的代碼已經(jīng)執(zhí)行完以后峡碉,這時就會去查看是否有微任務(wù)可以執(zhí)行近哟,然后發(fā)現(xiàn)了Promise和MutationObserver兩個微任務(wù),遂執(zhí)行之
3鲫寄、click事件會冒泡吉执,所以對應(yīng)的這次I/O會觸發(fā)兩次handler函數(shù)(一次在inner、一次在outer)地来,所以會優(yōu)先執(zhí)行冒泡的事件(早于其他的宏任務(wù))戳玫,也就是說會重復(fù)上述的邏輯
4、在執(zhí)行完同步代碼與微任務(wù)以后未斑,這時繼續(xù)向后查找有木有宏任務(wù)
5咕宿、因為我們觸發(fā)了setAttribute,實際上修改了DOM的屬性,這會導(dǎo)致頁面的重繪府阀,而這個set的操作是同步執(zhí)行的缆镣,也就是說requestAnimationFrame的回調(diào)會早于setTimeout所執(zhí)行
所以上面 執(zhí)行順序是:click -> promise -> observer -> click -> promise -> observer -> animationFrame -> animationFrame -> timeout -> timeout
node:
- Node也是單線程,但是在處理Event Loop上與瀏覽器稍微有些不同
setImmediate與setTimeout的區(qū)別:
在官方文檔中的定義试浙,setImmediate為一次Event Loop執(zhí)行完畢后調(diào)用董瞻。
setTimeout則是通過計算一個延遲時間后進(jìn)行執(zhí)行。
microTask:微任務(wù)田巴;
nextTick:process.nextTick钠糊;
timers:執(zhí)行滿足條件的 setTimeout 、setInterval 回調(diào)固额;
I/O callbacks:是否有已完成的 I/O 操作的回調(diào)函數(shù)眠蚂,來自上一輪的 poll 殘留;
poll:等待還沒完成的 I/O 事件斗躏,會因 timers 和超時時間等結(jié)束等待逝慧;
check:執(zhí)行 setImmediate 的回調(diào);
close callbacks:關(guān)閉所有的 closing handles 啄糙,一些 onclose 事件笛臣;
idle/prepare 等等:可忽略。
- macro-task(宏任務(wù)):包括整體代碼script隧饼,setTimeout沈堡,setInterval
- micro-task(微任務(wù)):Promise,process.nextTick
進(jìn)程和線程的區(qū)別:
- 線程是程序執(zhí)行的最小單位燕雁,而進(jìn)程是操作系統(tǒng)分配資源的最小單位诞丽;
- 一個進(jìn)程由一個或多個線程組成,線程是一個進(jìn)程中代碼的不同執(zhí)行路線
- 進(jìn)程之間相互獨立拐格,但同一進(jìn)程下的各個線程之間共享程序的內(nèi)存空間(包括代碼段僧免,數(shù)據(jù)集,堆等)及一些進(jìn)程級的資源(如打開文件和信號等)捏浊,某進(jìn)程內(nèi)的線程在其他進(jìn)程不可見懂衩;
- 調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多
- 進(jìn)程是操作系統(tǒng)資源分配的基本單位,而線程是任務(wù)調(diào)度和執(zhí)行的基本單位
- 內(nèi)存分配方面:
系統(tǒng)在運行的時候會為每個進(jìn)程分配不同的內(nèi)存空間金踪;
而對線程而言浊洞,除了CPU外,系統(tǒng)不會為線程分配內(nèi)存(線程所使用的資源來自其所屬進(jìn)程的資源)胡岔,線程組之間只能共享資源法希。 - 所處環(huán)境:在操作系統(tǒng)中能同時運行多個進(jìn)程(程序);而在同一個進(jìn)程(程序)中有多個線程同時執(zhí)行(通過CPU調(diào)度靶瘸,在每個時間片中只有一個線程執(zhí)行)
- 創(chuàng)建一個線程比進(jìn)程開銷刑摹尖淘;
- 線程之間通信更方便,同一個進(jìn)程下著觉,線程共享全局變量,靜態(tài)變量等數(shù)據(jù)惊暴,進(jìn)程之間的通信需要以通信的方式(IPC)進(jìn)行饼丘;(但多線程程序處理好同步與互斥是個難點)
瀏覽器都有哪些進(jìn)程?
1.Browser進(jìn)程(即上篇文章截圖里面的瀏覽器進(jìn)程):瀏覽器的主進(jìn)程(負(fù)責(zé)協(xié)調(diào)辽话、主控)肄鸽,只有一個。主要作用:
- 負(fù)責(zé)瀏覽器界面顯示油啤,與用戶交互典徘。如前進(jìn),后退等
- 負(fù)責(zé)各個頁面的管理益咬,創(chuàng)建和銷毀其他進(jìn)程
- 將渲染(Renderer)進(jìn)程得到的內(nèi)存中的Bitmap(位圖)逮诲,繪制到用戶界面上
- 網(wǎng)絡(luò)資源的管理,下載等
2幽告、第三方插件進(jìn)程:每種類型的插件對應(yīng)一個進(jìn)程梅鹦,僅當(dāng)使用該插件時才創(chuàng)建
3、GPU進(jìn)程:最多一個冗锁,用于3D繪制等
4齐唆、瀏覽器渲染進(jìn)程(即通常所說的瀏覽器內(nèi)核)(Renderer進(jìn)程,內(nèi)部是多線程的):主要作用為頁面渲染冻河,腳本執(zhí)行箍邮,事件處理等
瀏覽器內(nèi)核
簡單來說瀏覽器內(nèi)核是通過取得頁面內(nèi)容、整理信息(應(yīng)用CSS)叨叙、計算和組合最終輸出可視化的圖像結(jié)果锭弊,通常也被稱為渲染引擎。從上面我們可以知道摔敛,Chrome瀏覽器為每個tab頁面單獨啟用進(jìn)程廷蓉,因此每個tab網(wǎng)頁都有由其獨立的渲染引擎實例
瀏覽器內(nèi)核是多線程,在內(nèi)核控制下各線程相互配合以保持同步马昙,一個瀏覽器通常由以下常駐線程組成
- GUI 渲染線程
GUI渲染線程負(fù)責(zé)渲染瀏覽器界面HTML元素,當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時,該線程就會執(zhí)行桃犬。在Javascript引擎運行腳本期間,GUI渲染線程都是處于掛起狀態(tài)的,也就是說被”凍結(jié)”了.
- JavaScript引擎線程
Javascript引擎,也可以稱為JS內(nèi)核行楞,主要負(fù)責(zé)處理Javascript腳本程序攒暇,例如V8引擎。Javascript引擎線程理所當(dāng)然是負(fù)責(zé)解析Javascript腳本
- 定時觸發(fā)器線程
- 事件觸發(fā)線程
- 異步http請求線程