一、js線程
之所以js中有事件循環(huán),原因就是因為js是單線程的原因
1. 進程與線程的關(guān)系和區(qū)別?JS 單線程帶來的好處?
一個進程中可以有多個線程,比如渲染線程、JS 引擎線程、HTTP 請求線程等等。當(dāng)你發(fā)起一個請求時,其實就是創(chuàng)建了一個線程,當(dāng)請求結(jié)束后,該線程可能就會被銷毀。
注:瀏覽器是多進程、多線程的,JS是單線程的
瀏覽器每個標(biāo)簽頁是一個進程,每個進程里同時有js線程、網(wǎng)絡(luò)線程、渲染線程等
在 JS 運行的時候可能會阻止 UI 渲染,這說明了兩個線程是互斥的。這其中的原因是因為 JS 可以修改 DOM,如果在 JS 執(zhí)行的時候 UI 線程還在工作,就可能導(dǎo)致不能安全的渲染 UI。這其實也是一個單線程的好處,得益于 JS 是單線程運行的,可以達(dá)到節(jié)省內(nèi)存,節(jié)約上下文切換時間,沒有鎖的問題的好處。
對于鎖的問題,形象的來說就是當(dāng)我讀取一個數(shù)字 15 的時候,同時有兩個操作對數(shù)字進行了加減,這時候結(jié)果就出現(xiàn)了錯誤。解決這個問題也不難,只需要在讀取的時候加鎖,直到讀取完畢之前都不能進行寫入操作。z這就是鎖
2. 同步和異步函數(shù)
2.1 同步函數(shù):函數(shù)返回—>拿到預(yù)期結(jié)果
Math.sqrt(2)
console.log('Hi')
2.2 異步函數(shù):函數(shù)返回—>拿不到預(yù)期結(jié)果,還要通過一定的手段獲得結(jié)果
// 這是node 里面的函數(shù)
fs.readFile('foo.txt', 'utf8', function(err, data){
console.log(data)
}
//該函數(shù)需要在全部文件都讀取完之后才能console
異步過程:
- 注冊函數(shù):發(fā)起異步過程
- 回調(diào)函數(shù):處理結(jié)果
異步類型:
- 普通事件:click、resize
- 資源加載:load、error
- 定時器:setInterval、setTimeout
二、事件循環(huán)
1. 什么是執(zhí)行棧
可以把執(zhí)行棧認(rèn)為是一個存儲同步函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進后出的原則。
當(dāng)開始執(zhí)行 JS 代碼時,首先會執(zhí)行一個 main 函數(shù),然后執(zhí)行我們的代碼。根據(jù)先進后出的原則,后執(zhí)行的函數(shù)會先彈出棧,在圖中我們也可以發(fā)現(xiàn),foo 函數(shù)后執(zhí)行,當(dāng)執(zhí)行完畢后就從棧中彈出了。
function foo() {
throw new Error('error')
}
function bar() {
foo()
}
bar()

可以在上圖清晰的看到報錯在 foo 函數(shù),foo 函數(shù)又是在 bar 函數(shù)中調(diào)用的。
爆棧
當(dāng)我們使用遞歸的時候,因為??纱娣诺暮瘮?shù)是有限制的,一旦存放了過多的函數(shù)且沒有得到釋放的話,就會出現(xiàn)爆棧的問題
function bar() {
bar()
}
bar()

2. 消息隊列
對于同步任務(wù),按照順序既可,但是根據(jù)異步函數(shù)執(zhí)行時間長短不一樣,所以就有了消息隊列
消息指的是–>注冊異步任務(wù)時添加的回調(diào)函數(shù)
js遇到異步函數(shù)時,不會一直等待其結(jié)果,而是掛起,繼續(xù)執(zhí)行執(zhí)行棧中的任務(wù);只要異步操作執(zhí)行完成,就可以到消息隊列中去排隊,然后主線程空閑的時候,就可以從消息隊列中獲取消息并執(zhí)行其回調(diào)
三、事件循環(huán)
以一個代碼為例:
div.onclick = () => {
console.log('hi!')
}
- js引擎解析到該段代碼時,將onclick這個函數(shù)掛起
- 如果點擊了這個div,就是異步操作執(zhí)行完成,那么就將這個函數(shù)放入消息隊列中
- 當(dāng)執(zhí)行棧處于閑置狀態(tài)時,就從消息隊列中取出該任務(wù)對應(yīng)的回調(diào)放進執(zhí)行棧中執(zhí)行
四、 異步代碼執(zhí)行順序?
再次重復(fù)一下:
當(dāng)遇到異步的代碼時,會被掛起并在異步操作完成時加入到 消息隊列中。一旦執(zhí)行棧為空,js引擎就會從 消息 隊列中拿出該異步函數(shù)的回調(diào)并放入執(zhí)行棧中執(zhí)行,所以本質(zhì)上來說 JS 中的異步還是同步行為。

1. 異步任務(wù)的分類?
不同的任務(wù)源會被分配到不同的 消息隊列中,任務(wù)源可以分為 微任務(wù)(microtask) 和 宏任務(wù)
- 宏任務(wù):由宿主對象發(fā)起的任務(wù)(setTimeout)
- 宏任務(wù)包括 script, setTimeout ,setInterval ,setImmediate ,I/O,UI rendering。
- 微任務(wù):由js引擎發(fā)起的任務(wù)(promise)
- 微任務(wù)包括 process.nextTick,promise ,MutationObserver。
宿主對象:
由js 宿主環(huán)境提供的對象,他們的行為完全由宿主環(huán)境決定
- 固有(new image 創(chuàng)建img 元素)
- 用戶可創(chuàng)建的(document.createElement就可以創(chuàng)建Dom)
五、Event Loop (事件循環(huán))執(zhí)行順序如下所示:
- 在異步事件執(zhí)行完操作后會放入一個執(zhí)行隊列里,而根據(jù)這個異步事件的類型會被放入對應(yīng)的宏任務(wù)隊列或者微任務(wù)隊列中
- 當(dāng)執(zhí)行棧為空時,主線程會先去執(zhí)行微任務(wù)隊列中對應(yīng)的回調(diào)函數(shù),再去執(zhí)行宏任務(wù)隊列中的任務(wù)(當(dāng)然是取出放入執(zhí)行棧中執(zhí)行)
- 在一次循環(huán)中,微任務(wù)永遠(yuǎn)在宏任務(wù)之前執(zhí)行
heap 堆(主要用于內(nèi)存分配)
- 對象
stack 執(zhí)行棧/方法調(diào)用棧(先進后出)
- 當(dāng)JS引擎執(zhí)行函數(shù)時,會把函數(shù)按照執(zhí)行順序放入stack 執(zhí)行棧,并按照執(zhí)行完畢的順序從執(zhí)行棧里移除。(先進后出)
- 如果一直在調(diào)用函數(shù)而沒有結(jié)束(自調(diào)用死循環(huán)),執(zhí)行棧容量會達(dá)到上限,報錯。 這就是 爆棧
stack queue 任務(wù)隊列(異步)
- 如果調(diào)用到異步函數(shù),會把異步函數(shù)先放入stack queue 任務(wù)隊列,繼續(xù)執(zhí)行stack 執(zhí)行棧里的同步函數(shù)。當(dāng)執(zhí)行棧的函數(shù)全部執(zhí)行完畢并移除,再把任務(wù)隊列里的異步函數(shù)按照加入任務(wù)隊列的先后順序放入執(zhí)行棧,繼續(xù)執(zhí)行。
- JS中用于儲存待執(zhí)行回調(diào)函數(shù)的隊列包含兩個不同特定的列隊:微隊列、宏隊列。
microtask 微任務(wù)(優(yōu)先級高)
- promise,process.nextTick,Object.obverse,MutationObserver
macrotask 宏任務(wù)(優(yōu)先級低)
- 定時器,setImmediate,I/O(鍵盤、網(wǎng)絡(luò)),UI rending
事件循環(huán)過程
- 執(zhí)行全局 JS 同步代碼,有的是同步語句,有的是異步語句(如setTimeout等)。放入 stack 執(zhí)行棧;
- Event Loop 事件循環(huán) 不斷檢查 stack 執(zhí)行棧 是否為空;
- 為空時檢查 task queue 任務(wù)隊列(微隊列、宏隊列) 是否有異步任務(wù), 如果有則開始執(zhí)行;
- 在本次循環(huán)中,取出 microtask queue 微隊列 中第一個 microtask 微任務(wù),放入 stack 執(zhí)行棧 中執(zhí)行,完成后 microtask queue 微隊列 長度減1;
- 繼續(xù)取出 microtask queue 微隊列 中第一個 microtask 微任務(wù),放入 stack 執(zhí)行棧 中執(zhí)行,以此類推,直到把 microtask queue 微隊列 清空;
注意:如果在執(zhí)行 microtask 微任務(wù) 的過程中,又產(chǎn)生了新的 microtask 微任務(wù) ,會加入到隊尾,也在本次循環(huán)中執(zhí)行;
- 取出 macrotask queue 宏隊列 中第一個 macrotask 宏任務(wù),放入 stack 執(zhí)行棧 中執(zhí)行;
即 所有微任務(wù) microtask + 一個宏任務(wù) macrotask 。(所以多個網(wǎng)絡(luò)請求可以同時處于等待狀態(tài))
7.執(zhí)行完畢后,調(diào)用棧Stack為空;
8.重復(fù) Event Loop 事件循環(huán) (第2-7步)