瀏覽器是多進程也是多線程的
??每打開一個瀏覽器頁面就相當(dāng)于創(chuàng)建了一個獨立的瀏覽器進程桐款。瀏覽器的線程包括:
??1.GUI渲染線程
??負(fù)責(zé)渲染瀏覽器界面
,解析HTML盏混、CSS,構(gòu)建DOM樹和RenderObject樹续搀,布局和繪制等帐我。當(dāng)界面需要回流坎炼、重繪時,該線程就會執(zhí)行
拦键。 注意:GUI渲染線程與JS引擎線程是互斥的谣光,當(dāng)JS引擎執(zhí)行時GUI線程會被掛起。
??2.JS引擎線程
??也稱為JS內(nèi)核芬为,負(fù)責(zé)處理JavaScript腳本程序
(例如V8引擎)萄金,同樣注意,GUI渲染線程與JS引擎線程是互斥的媚朦,如果JS執(zhí)行時間過長氧敢,就會造成頁面渲染阻塞,這也是為什么JS腳本寫在HTML询张、CSS下面的原因孙乖。
??3.事件觸發(fā)線程
??用于將鼠標(biāo)點擊事件等添加到任務(wù)隊列中,運行并返回結(jié)果份氧,等待JS引擎處理唯袄。
??4.定時器觸發(fā)線程
??用于將定時器任務(wù)添加到任務(wù)隊列中,運行并返回結(jié)果蜗帜,等待JS引擎處理恋拷。
??5.異步http請求線程
??用于將異步請求添加到任務(wù)隊列中,運行并返回結(jié)果厅缺,等待JS引擎處理蔬顾。
總結(jié):js引擎線程就是主線程,3湘捎、4诀豁、5線程是子線程,HTML5支持的Web Worker就是處理任務(wù)隊列中的程序消痛,也屬于子線程且叁。
load事件與DOMContentLoaded事件的先后
??DOMContentLoaded事件觸發(fā)時,僅當(dāng)DOM加載完成秩伞,不包括樣式逞带、圖片。當(dāng)onload事件觸發(fā)時纱新,頁面上所有的DOM展氓、樣式、圖片脸爱、腳本都已經(jīng)加載完成遇汞。
CSS加載是否會阻塞DOM樹渲染
??css是由單獨的下載線程異步下載的,不會阻塞dom樹解析,所以放在頭部引入空入,不過會阻塞render樹渲染络它,因為渲染需要等css加載完畢。
JavaScript執(zhí)行機制
??JavaScript語言的一大特點就是單線程歪赢,也就是說化戳,一個時間段內(nèi)只能做一件事情。為什么JavaScript不能有多個線程呢埋凯?這樣能提高效率点楼。假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容白对,另一個線程刪除了這個節(jié)點掠廓,這兩個線程就會沖突?所以為了避免這個問題甩恼,從一誕生蟀瞧,JavaScript就是單線程。這已經(jīng)成了這門語言的核心特征媳拴,將來也不會變黄橘。
??單線程意味著所有任務(wù)都需要排隊,前一個任務(wù)結(jié)束才會執(zhí)行后一個任務(wù)屈溉。如果前一個任務(wù)耗時很長,后一個任務(wù)就不得不等待抬探,(造成IO設(shè)備很慢(比如AJAX請求)子巾,那么CPU不得不等著結(jié)果出來再往下執(zhí)行,)導(dǎo)致頁面渲染阻塞小压,用戶體驗不好线梗。于是,所有任務(wù)可以分成兩種怠益,一種是同步任務(wù)仪搔,另一種是異步任務(wù)。同步任務(wù)指的是蜻牢,在主線程上排隊執(zhí)行的任務(wù)烤咧,前一個任務(wù)執(zhí)行完畢,就會執(zhí)行后一個任務(wù)抢呆;異步任務(wù)指的是煮嫌,不進入主線程、而進入"任務(wù)隊列"的任務(wù)抱虐,等到主線程空了昌阿,就會讀取"任務(wù)隊列",有運行結(jié)果的異步任務(wù)就會被推入到主線程中執(zhí)行,主線程會不斷重復(fù)這個過程懦冰。這就是JavaScript的運行機制灶轰,又稱為Event Loop(事件循環(huán))。
JS 異步解決方案的發(fā)展歷程以及優(yōu)缺點刷钢。
- 回調(diào)函數(shù)(callback)
setTimeout(() => {
// callback 函數(shù)體
}, 1000)
缺點:回調(diào)地獄笋颤,不能用 try catch 捕獲錯誤,不能 return
回調(diào)地獄的根本問題在于:
缺乏順序性: 回調(diào)地獄導(dǎo)致的調(diào)試?yán)щy闯捎,和大腦的思維方式不符椰弊;
嵌套函數(shù)存在耦合性,一旦有所改動瓤鼻,就會牽一發(fā)而動全身秉版,即(控制反轉(zhuǎn));
嵌套函數(shù)過多的多話茬祷,很難處理錯誤清焕。
ajax('XXX1', () => {
// callback 函數(shù)體
ajax('XXX2', () => {
// callback 函數(shù)體
ajax('XXX3', () => {
// callback 函數(shù)體
})
})
})
優(yōu)點:解決了同步的問題(只要有一個任務(wù)耗時很長,后面的任務(wù)都必須排隊等著祭犯,會拖延整個程序的執(zhí)行)秸妥。
- Promise
Promise 就是為了解決 callback 的問題而產(chǎn)生的。
Promise 實現(xiàn)了鏈?zhǔn)秸{(diào)用沃粗,也就是說每次 then 后返回的都是一個全新 Promise粥惧,如果我們在 then 中 return ,return 的結(jié)果會被 Promise.resolve() 包裝最盅。
優(yōu)點:解決了回調(diào)地獄的問題突雪。
ajax('XXX1')
.then(res => {
// 操作邏輯
return ajax('XXX2')
}).then(res => {
// 操作邏輯
return ajax('XXX3')
}).then(res => {
// 操作邏輯
})
缺點:無法取消 Promise ,錯誤需要通過回調(diào)函數(shù)來捕獲涡贱。
- Generator
優(yōu)點:可以控制異步的開始和暫停咏删。
缺點:必須配合 co 自執(zhí)行器使用。
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
- Async/await
async问词、await 是異步的終極解決方案督函。
優(yōu)點是:代碼清晰,不用像 Promise 寫一大堆 then 鏈激挪,處理了回調(diào)地獄的問題辰狡;
缺點:await 將異步代碼改造成同步代碼,如果多個異步操作沒有依賴性而使用 await 會導(dǎo)致性能上的降低灌灾。
async function test() {
// 以下代碼沒有依賴性的話搓译,完全可以使用 Promise.all 的方式
// 如果有依賴性的話,其實就是解決回調(diào)地獄的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
下面來看一個使用 await 的例子:
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
對于以上代碼你可能會有疑惑锋喜,讓我來解釋下原因:
首先函數(shù) b 先執(zhí)行些己,在執(zhí)行到 await 10 之前變量 a 還是 0豌鸡,因為 await 內(nèi)部實現(xiàn)了generator ,generator 會保留堆棧中東西段标,所以這時候 a = 0 被保存了下來涯冠;
因為 await 是異步操作,后來的表達式不返回 Promise 的話逼庞,就會包裝成Promise.reslove(返回值)蛇更,然后會去執(zhí)行函數(shù)外的同步代碼;
同步代碼執(zhí)行完畢后開始執(zhí)行異步代碼赛糟,將保存下來的值拿出來使用派任,這時候 a = 0 + 10。
上述解釋中提到了 await 內(nèi)部實現(xiàn)了 generator璧南,其實 await 就是 generator 加上Promise的語法糖掌逛,且內(nèi)部實現(xiàn)了自動執(zhí)行 generator。如果你熟悉 co 的話司倚,其實自己就可以實現(xiàn)這樣的語法糖豆混。
本題鏈接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/11
try...catch
try...catch是被設(shè)計成捕獲當(dāng)前執(zhí)行環(huán)境的異常,意思是只能捕獲同步代碼里面的異常动知,異步調(diào)用里面的異常無法捕獲皿伺。
第一種方式:異常出現(xiàn)在異步調(diào)用里面,try..catch無法捕獲盒粮。
第二重方式:try..catch是寫在異步代碼里面鸵鸥,相對于try里面的所有執(zhí)行都是同步代碼,所以能捕獲丹皱。
定時器
??除了放置異步任務(wù)的事件脂男,"任務(wù)隊列"還可以放置定時器,它在"任務(wù)隊列"的尾部添加一個事件种呐,因此要等到同步任務(wù)和"任務(wù)隊列"現(xiàn)有的事件都處理完,才會得到執(zhí)行弃甥。
回調(diào)函數(shù)的使用場合
資源加載:動態(tài)加載js文件后執(zhí)行回調(diào)爽室,加載 iframe后執(zhí)行回調(diào),ajax操作回調(diào)淆攻,圖片加載完成執(zhí)行回調(diào)阔墩。
事件循環(huán)進階:macrotask與microtask
??macrotask(task):宏任務(wù),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(wù)(包括每次從事件隊列中獲取一個事件回調(diào)并放到執(zhí)行棧中執(zhí)行)瓶珊,包括主代碼塊(同步)啸箫、setTimeout、setInterval等伞芹。
??microtask(jobs):微任務(wù)忘苛,在task執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)蝉娜,包括promise的then、process.nextTick(微任務(wù)隊列扎唾,優(yōu)先級更高)等
優(yōu)先級:同步 > 微任務(wù)(異步) > 宏任務(wù)(異步)
優(yōu)先級:process.nextTick > promise
執(zhí)行過程:macrotask > microtask > 渲染 > (下一輪)macrotask > microtask > 渲染...
注釋:1.線程可以理解為做事召川,一個線程表示一個時間段只能做一件事,多個線程表示一個時間段可以同時做多件事胸遇。
2.CPU:中央處理器是一塊超大規(guī)模的集成電路荧呐,是一臺計算機的運算核心和控制核心。它的功能主要是解釋計算機指令以及處理計算機軟件中的數(shù)據(jù)纸镊。
3.I/O設(shè)備是指輸入/輸出設(shè)備接口倍阐。
http://www.reibang.com/p/334287bdbdd0
參考網(wǎng)址:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html(js執(zhí)行機制)
https://www.cnblogs.com/cangqinglang/p/8963557.html(js執(zhí)行機制)
https://zhuanlan.zhihu.com/p/43282197(css加載會造成阻塞嗎)
https://blog.csdn.net/liwenfei123/article/details/80670330(宏任務(wù)和微任務(wù))