JavaScript的進程和線程

一、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
異步過程:
  1. 注冊函數(shù):發(fā)起異步過程
  2. 回調(diào)函數(shù):處理結(jié)果
異步類型:
  1. 普通事件:click、resize
  2. 資源加載:load、error
  3. 定時器: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()
image.png

可以在上圖清晰的看到報錯在 foo 函數(shù),foo 函數(shù)又是在 bar 函數(shù)中調(diào)用的。

爆棧

當(dāng)我們使用遞歸的時候,因為??纱娣诺暮瘮?shù)是有限制的,一旦存放了過多的函數(shù)且沒有得到釋放的話,就會出現(xiàn)爆棧的問題

function bar() {
  bar()
}
bar()
image.png

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!')
}
  1. js引擎解析到該段代碼時,將onclick這個函數(shù)掛起
  2. 如果點擊了這個div,就是異步操作執(zhí)行完成,那么就將這個函數(shù)放入消息隊列中
  3. 當(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 中的異步還是同步行為。

image.png

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í)行順序如下所示:

  1. 在異步事件執(zhí)行完操作后會放入一個執(zhí)行隊列里,而根據(jù)這個異步事件的類型會被放入對應(yīng)的宏任務(wù)隊列或者微任務(wù)隊列中
  2. 當(dāng)執(zhí)行棧為空時,主線程會先去執(zhí)行微任務(wù)隊列中對應(yīng)的回調(diào)函數(shù),再去執(zhí)行宏任務(wù)隊列中的任務(wù)(當(dāng)然是取出放入執(zhí)行棧中執(zhí)行)
  3. 在一次循環(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)過程

  1. 執(zhí)行全局 JS 同步代碼,有的是同步語句,有的是異步語句(如setTimeout等)。放入 stack 執(zhí)行棧;
  2. Event Loop 事件循環(huán) 不斷檢查 stack 執(zhí)行棧 是否為空;
  3. 為空時檢查 task queue 任務(wù)隊列(微隊列、宏隊列) 是否有異步任務(wù), 如果有則開始執(zhí)行;
  4. 在本次循環(huán)中,取出 microtask queue 微隊列 中第一個 microtask 微任務(wù),放入 stack 執(zhí)行棧 中執(zhí)行,完成后 microtask queue 微隊列 長度減1;
  5. 繼續(xù)取出 microtask queue 微隊列 中第一個 microtask 微任務(wù),放入 stack 執(zhí)行棧 中執(zhí)行,以此類推,直到把 microtask queue 微隊列 清空;

注意:如果在執(zhí)行 microtask 微任務(wù) 的過程中,又產(chǎn)生了新的 microtask 微任務(wù) ,會加入到隊尾,也在本次循環(huán)中執(zhí)行;

  1. 取出 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步)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容