一 進程與線程
進程和線程的概念用較為官方的術(shù)語描述來說是這樣的
1.進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)
2.線程是cpu調(diào)度的最小單位(線程是建立在進程的基礎(chǔ)上的一次程序運行單位,一個進程中可以有多個線程)
用LOL來比喻的話(舉例子可能不太貼切峦失,因為本人比較喜歡玩lol 所以用這個舉例子讓自己來加深印象)
一個進程就是一局游戲,每一局游戲都要有野怪小兵這些資源绿映;
每局游戲都相互獨立互不干涉;
一個線程就是一個英雄腐晾;
一局游戲里有多個英雄(一個進程有多個線程)叉弦;
英雄之間共享資源
映射關(guān)系為
一局游戲的野怪和小兵 > 系統(tǒng)分配的內(nèi)存
每一局游戲相互獨立 > 進程之間相互獨立
打團戰(zhàn) -> 多個線程在進程中協(xié)作完成任務(wù)
一局游戲可能一個玩也可能多個人玩>一個進程由一個或多個線程組成
英雄之間共享野怪小兵誰都可以吃 -> 同一進程下的各個線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集藻糖、堆等)
二 瀏覽器是多進程的
瀏覽器可以運行是因為系統(tǒng)給分配了資源和內(nèi)存淹冰,瀏覽器每一個tab頁都需要資源和內(nèi)存,所以每一個tab頁面都對應(yīng)至少一個進程颖御,在這里瀏覽器應(yīng)該也有自己的優(yōu)化機制榄棵,有時候打開多個tab頁后凝颇,可以在Chrome任務(wù)管理器中看到潘拱,有些進程被合并了 (所以每一個Tab標簽對應(yīng)一個進程并不一定是絕對的)
知道了瀏覽器是多進程后,再來看看它到底包含哪些進程:(為了簡化理解拧略,僅列舉主要進程)
1. Browser進程
瀏覽器的主進程(負責協(xié)調(diào)芦岂、主控),只有一個垫蛆。
負責瀏覽器界面顯示禽最,與用戶交互。如前進袱饭,后退等
負責各個頁面的管理川无,創(chuàng)建和銷毀其他進程
將Renderer進程得到的內(nèi)存中的Bitmap,繪制到用戶界面上
網(wǎng)絡(luò)資源的管理虑乖,下載等
2. 第三方插件進程
每種類型的插件對應(yīng)一個進程懦趋,僅當使用該插件時才創(chuàng)建
3. GPU進程
最多一個,用于3D繪制等
4. 瀏覽器渲染進程(瀏覽器內(nèi)核)(Renderer進程疹味,內(nèi)部是多線程的)
默認每個Tab頁面一個進程仅叫,互不影響帜篇。
主要作用為
頁面渲染,腳本執(zhí)行诫咱,事件處理等
三 瀏覽器渲染進程
作為前端開發(fā)工程師我們最主要的關(guān)注點還是渲染進程笙隙。
可以這樣理解,頁面的渲染坎缭,JS的執(zhí)行竟痰,事件的循環(huán),都在這個進程內(nèi)進行幻锁,渲染進程是多線程的
接下來重點分析這個進程的常駐線程凯亮。
1.GUI渲染進程
1.負責渲染瀏覽器界面,解析HTML哄尔,CSS假消,構(gòu)建DOM樹和RenderObject樹,布局和繪制等岭接。
2.當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時富拗,該線程就會執(zhí)行。
3.注意鸣戴,GUI渲染線程與JS引擎線程是互斥的啃沪,當JS引擎執(zhí)行時GUI線程會被掛起(相當于被凍結(jié)了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執(zhí)行窄锅。
2.JS引擎線程
1.也稱為JS內(nèi)核创千,負責處理Javascript腳本程序。(例如V8引擎)
2.JS引擎線程負責解析Javascript腳本入偷,運行代碼追驴。
3.JS引擎一直等待著任務(wù)隊列中任務(wù)的到來,然后加以處理疏之,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
4.同樣注意殿雪,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時間過長锋爪,這樣就會造成頁面的渲染不連貫丙曙,導(dǎo)致頁面渲染加載阻塞。
3.事件處理線程
1.歸屬于瀏覽器而不是JS引擎其骄,用來控制事件循環(huán)(可以理解亏镰,JS引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助)
2.當JS引擎執(zhí)行代碼塊如setTimeOut時(也可來自瀏覽器內(nèi)核的其他線程,如鼠標點擊拯爽、AJAX異步請求等)索抓,會將對應(yīng)任務(wù)添加到事件線程中
3.當對應(yīng)的事件符合觸發(fā)條件被觸發(fā)時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
4.注意纸兔,由于JS的單線程關(guān)系惰瓜,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執(zhí)行)
4.定時器線程
1.傳說中的setInterval與setTimeout所在線程
2.瀏覽器定時計數(shù)器并不是由JavaScript引擎計數(shù)的,(因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準確)
3.因此通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件隊列中汉矿,等待JS引擎空閑后執(zhí)行)
4.注意崎坊,W3C在HTML標準中規(guī)定,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms洲拇。
5.http異步請求線程
1.在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求
2.將檢測到狀態(tài)變更時奈揍,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件赋续,將這個回調(diào)再放入事件隊列中男翰。再由JavaScript引擎執(zhí)行。
四 事件循環(huán)
上文說了幾個線程
js引擎線程
事件觸發(fā)線程
定時觸發(fā)器線程
然后再理解一個概念:
JS分為同步任務(wù)和異步任務(wù)
1.同步任務(wù)都在主線程上執(zhí)行纽乱,形成一個執(zhí)行棧
2.主線程之外蛾绎,事件觸發(fā)線程管理著一個任務(wù)隊列,只要異步任務(wù)有了運行結(jié)果鸦列,就在任務(wù)隊列之中放置一個事件
3.一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢(此時JS引擎空閑)租冠,系統(tǒng)就會讀取任務(wù)隊列,將可運行的異步任務(wù)添加到可執(zhí)行棧中薯嗤,開始執(zhí)行
主線程運行時會產(chǎn)生執(zhí)行棧顽爹,棧中的代碼調(diào)用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發(fā)條件后骆姐,如ajax請求完畢)
而棧中的代碼執(zhí)行完畢镜粤,就會讀取事件隊列中的事件,去執(zhí)行那些回調(diào)
如此循環(huán)
注意玻褪,總是要等待棧中的代碼執(zhí)行完畢后才會去讀取事件隊列中的事件
五 宏任務(wù)微任務(wù)
上文中將JS事件循環(huán)機制梳理了一遍肉渴,在ES5的情況是夠用了,但是在ES6盛行的現(xiàn)在归园,仍然會遇到一些問題黄虱,譬如下面這題:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
它的正確執(zhí)行順序是這樣子的:
script start
script end
promise1
promise2
setTimeout
為什么呢稚矿?因為Promise里有了一個一個新的概念:microtask庸诱。微任務(wù)
JS中分為兩種任務(wù)類型:macrotask和microtask,在ECMAScript中晤揣,microtask稱為jobs桥爽,macrotask可稱為task。
它們的定義昧识?區(qū)別钠四?簡單點可以按如下理解:
macrotask(又稱之為宏任務(wù)),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(wù)(包括每次從事件隊列中獲取一個事件回調(diào)并放到執(zhí)行棧中執(zhí)行)
每一個task會從頭到尾將這個任務(wù)執(zhí)行完畢,不會執(zhí)行其它
瀏覽器為了能夠使得JS內(nèi)部task與DOM任務(wù)能夠有序的執(zhí)行缀去,會在一個task執(zhí)行結(jié)束后侣灶,在下一個 task 執(zhí)行開始前,對頁面進行重新渲染
microtask(又稱為微任務(wù))缕碎,可以理解是在當前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
也就是說褥影,在當前task任務(wù)后,下一個task之前咏雌,在渲染之前
所以它的響應(yīng)速度相比setTimeout(setTimeout是task)會更快凡怎,因為無需等渲染
也就是說,在某一個macrotask執(zhí)行完后赊抖,就會將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)
分別很么樣的場景會形成macrotask和microtask呢统倒?
macrotask:主代碼塊,setTimeout氛雪,setInterval等(可以看到房匆,事件隊列中的每一個事件都是一個macrotask)
microtask:Promise,process.nextTick等
再根據(jù)線程來理解下:
macrotask中的事件都是放在一個事件隊列中的报亩,而這個隊列由事件觸發(fā)線程維護
microtask中的所有微任務(wù)都是添加到微任務(wù)隊列(Job Queues)中坛缕,等待當前macrotask執(zhí)行完畢后執(zhí)行,而這個隊列由JS引擎線程維護(這點由自己理解+推測得出捆昏,因為它是在主線程下無縫執(zhí)行的)
所以赚楚,總結(jié)下運行機制:
執(zhí)行一個宏任務(wù)(棧中沒有就從事件隊列中獲取)
執(zhí)行過程中如果遇到微任務(wù)骗卜,就將它添加到微任務(wù)的任務(wù)隊列中
宏任務(wù)執(zhí)行完畢后宠页,立即執(zhí)行當前微任務(wù)隊列中的所有微任務(wù)(依次執(zhí)行)
當前宏任務(wù)執(zhí)行完畢,開始檢查渲染寇仓,然后GUI線程接管渲染
渲染完畢后举户,JS線程繼續(xù)接管,開始下一個宏任務(wù)(從事件隊列中獲缺榉场)
最后附上幾道題
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
})
console.log('script end');
console.log('1');
setTimeout(function() {
console.log('2');
new Promise(function(resolve) {
console.log('3');
resolve();
}).then(function() {
console.log('4');
})
})
new Promise(function(resolve) {
console.log('5');
resolve();
}).then(function() {
console.log('6');
})
setTimeout(function() {
console.log('7');
})
setTimeout(function() {
console.log('8');
new Promise(function(resolve) {
console.log('9');
resolve();
}).then(function() {
console.log('10');
})
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12');
})
console.log('13');
const promise = new Promise((resolve, reject) => {
console.log("1");
setTimeout(() => {
console.log("2");
setTimeout(() => {
console.log('4');
})
resolve('success');
}, 1000)
})
console.log('3');
promise.then((res) => {
return new Error('error!!!')
}).then((res) => {
console.log('then:', res)
}).catch((err) => {
console.log('catch:', err)
})
promise.then((res) => {
console.log(res)
}).catch((err) => {
console.log('catch:', err);
})
歡迎大家在評論區(qū)寫出執(zhí)行順序