摘要:前言是以單線程的形式運(yùn)行在宿主環(huán)境下,采用了回調(diào)的形式來解決異步任務(wù)。線程中步就是在瀏覽器下的。
前言
javascript 是以單線程的形式運(yùn)行在宿主環(huán)境下,javascript 采用了回調(diào)的形式來解決異步任務(wù)。
為什么是單線程?javascript 的最開始的出現(xiàn)是為了給 web 頁面增添一些動態(tài)的效果,那么就避免不了獲取頁面上的元素信息,如果 javascript 是以多線程的形式運(yùn)行在瀏覽器內(nèi),如果兩個線程內(nèi)的 javascript 同時去獲取/修改,某個頁面上的元素,那么瀏覽器該讓哪個 javascript 線程擁有獲取/修改該元素的權(quán)限呢?由于元素的信息會經(jīng)常性的發(fā)生變化,那么又改如何去同步各個線程內(nèi)所保存的元素信息呢?
所以綜合以上問題, javascript 是單線程的原因就顯示意見了,單線程在執(zhí)行時,對于元素信息的引用在同一時間僅可能只有一個,那么以上所有的問題都不存在了。
什么是異步任務(wù)?任何代碼在執(zhí)行時,都會碰到一些需要經(jīng)過大量時間運(yùn)算或是等待的代碼,在瀏覽器的環(huán)境下,常見的就是 http 任務(wù),比如:資源的加載(圖片的 onload 事件),ajax 的請求(XMLHttpRequest 的 onLoad 事件)還有頁面元素的點(diǎn)擊事件以及定時器等。
以上的任務(wù)都極其的耗時而且會受環(huán)境的影響,如果同步執(zhí)行的話就會造成 javascript 執(zhí)行的卡頓,而 javascript 又是單線程的形式存在在瀏覽器端,為了使得 javascript 的執(zhí)行不受到影響,javascript 會將這些任務(wù)執(zhí)行放在另一個環(huán)境下,而將這些任務(wù)執(zhí)行完成后的需要執(zhí)行的函數(shù)給保存下來(也就是回調(diào)),這也是為什么一定要寫一個回調(diào)的原因。當(dāng)另一個環(huán)境下通知 javascript 線程該任務(wù)已完成,并將任務(wù)數(shù)據(jù)給到 javascript 線程,javascript 再去保存的回調(diào)中尋找該任務(wù)對應(yīng)的回調(diào),將數(shù)據(jù)當(dāng)做參數(shù)并執(zhí)行該回調(diào)。
Event Loop上面大概簡述了下 javascript 為什么要以異步回調(diào)的形式來處理一些耗時任務(wù),那么接下來就說說 javascript 到底是如何處理這些異步回調(diào)的。
從代碼入手
// a.js let image = new Image(); image.src = "image url"; image.onload = () => { // image 加載成功回調(diào) } image.onerror = () => { // image 加載失敗回調(diào) }
javascript 會從上到下執(zhí)行該代碼,當(dāng)執(zhí)行到 image.src = "image url" 時,javascript 線程通知瀏覽器圖片加載程序去加載相應(yīng)圖片,然后 javascript 繼續(xù)執(zhí)行剩下的代碼,當(dāng)執(zhí)行到 onload 和 onerror 時,javascript 僅僅是保存了這兩個函數(shù)而已(保存回調(diào))。
當(dāng)瀏覽器圖片加載程序加載好圖片,就會通知 javascript 線程, image 已加載完畢,如果沒有發(fā)生錯誤,那么 javascript 在接收到該信號以后就會執(zhí)行 image.onload 方法,如果通知回來是加載失敗,那么就會執(zhí)行 image.onerror 方法。
事件隊(duì)列按照上面所說,并結(jié)合最開始說的,如果圖片加載程序加載好圖片返回加載成功的信號時 javascript 正在處理別的任務(wù),由于 javascript 是單線程不能同時處理多個任務(wù),那么這個加載成功的信號就會被擱置,放在一個事件隊(duì)列中,javascript 線程在處理好當(dāng)前的任務(wù)后就會去事件隊(duì)列中取出一個事件并執(zhí)行響應(yīng)的回調(diào)。
Loop在真正的瀏覽器環(huán)境下,異步任務(wù)的信號每時每刻都會發(fā)生(比如設(shè)置的定期器,用戶的行為,ajax等),那么每時每刻都會有新的任務(wù)信號進(jìn)入事件隊(duì)列中,所以在瀏覽器中 javascript 的執(zhí)行會有以下的效果:
以下為 javascript 線程執(zhí)行的內(nèi)容
加載 script 所對應(yīng)的 javascript 腳本
執(zhí)行 javascript 代碼,注冊異步任務(wù),保存回調(diào)函數(shù)
引入的腳本所有代碼執(zhí)行完畢
一些 UI 渲染(該步驟不一定會有)
取事件隊(duì)列中最早進(jìn)入的事件,并在事件隊(duì)列中刪除該事件
執(zhí)行該事件對應(yīng)的回調(diào)代碼
回調(diào)代碼執(zhí)行完畢
一些 UI 渲染(該步驟不一定會有)
回到步驟 5
1 - 4 步是瀏覽器加載 javascript 所必須執(zhí)行的,可以認(rèn)為是注冊異步任務(wù)最開始的地方。
步驟 6 執(zhí)行回調(diào)的過程中可能會產(chǎn)生新的回調(diào),比如在 ajax 請求成功回調(diào)中注冊了頁面元素的點(diǎn)擊事件
以下為瀏覽器相關(guān)程序的內(nèi)容(異步任務(wù))
接收到 javascript 注冊的異步任務(wù)
執(zhí)行任務(wù)
任務(wù)完成后在事件隊(duì)列中推入成功事件
任務(wù)失敗后在事件隊(duì)列中推入失敗事件
這樣下來,javascript 線程就會持續(xù)不斷的執(zhí)行,也不會因?yàn)楹臅r任務(wù)而暫停執(zhí)行。
javascript 線程中 5 - 9 步就是在瀏覽器下的 Event Loop 。
圖解heap 回調(diào)函數(shù)保存處(堆)
stack 可以認(rèn)為是主線程執(zhí)行的地方(棧)
callback queue 事件隊(duì)列
WebAPIs 瀏覽器中處理 javascript 發(fā)出異步任務(wù)的程序
macro task 與 micro taskES6 出現(xiàn)之前,只有一個事件隊(duì)列,ES6 出現(xiàn)后,多了一個事件隊(duì)列,叫 micro task (微任務(wù)),用來專門放在一些優(yōu)先級較高的任務(wù),而之前實(shí)現(xiàn)的事件隊(duì)列就叫做 macro task (宏任務(wù))。
那么多了一個事件隊(duì)列,事件的讀取也發(fā)生了變化
加載 script 所對應(yīng)的 javascript 腳本
執(zhí)行 javascript 代碼,注冊異步任務(wù),保存回調(diào)函數(shù)
引入的腳本所有代碼執(zhí)行完畢
一些 UI 渲染(該步驟不一定會有)
讀取微任務(wù)事件隊(duì)列中最早進(jìn)入的事件并刪除該事件,有則進(jìn)入下一步,沒有執(zhí)行第 7 步
執(zhí)行該任務(wù)對應(yīng)的回調(diào),執(zhí)行結(jié)束后回到第 5 步
讀取宏任務(wù)事件隊(duì)列中最早進(jìn)入的事件
執(zhí)行該任務(wù)事件對應(yīng)的回調(diào)
讀取微任務(wù)事件隊(duì)列中最早進(jìn)入的事件并刪除該事件,有則進(jìn)入下一步,沒有執(zhí)行第 11 步
執(zhí)行該任務(wù)對應(yīng)的回調(diào),執(zhí)行結(jié)束后回到第 9 步
宏任務(wù)回調(diào)代碼執(zhí)行完畢
一些 UI 渲染(該步驟不一定會有)
回到步驟 5
就是當(dāng)每次 javascript 線程任務(wù)執(zhí)行結(jié)束后,會優(yōu)先處理微任務(wù)事件隊(duì)列中的事件,與宏任務(wù)不一樣的地方在于,瀏覽器會將微任務(wù)事件隊(duì)列中的事件一次性全部處理完在進(jìn)行 UI 渲染。
圖解能產(chǎn)生微任務(wù)的方式:
MutationObserver
Promise.then catch finally
能產(chǎn)生宏任務(wù)的方式:
setTimeout
setInterval
用戶行為
Image#onload
XMLHttpRequest
requestAnimationFrame
參考event-loop
JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop
深入理解js事件循環(huán)機(jī)制(瀏覽器篇)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/103379.html
摘要:何為事件循環(huán)機(jī)制的任務(wù)分兩種,分別是同步任務(wù)和異步任務(wù)。如上圖所示主線程在執(zhí)行代碼的時候,遇到異步任務(wù)進(jìn)入并注冊回調(diào)函數(shù),有了運(yùn)行結(jié)果后將它添加到事件隊(duì)列中,然后繼續(xù)執(zhí)行下面的代碼,直到同步代碼執(zhí)行完。 我們知道,JavaScript作為瀏覽器的腳本語言,起初是為了與用戶交互和操作DOM,為了避免因?yàn)橥瑫r操作了同一DOM節(jié)點(diǎn)而引起沖突,被設(shè)計(jì)成為一種單線程語言。而單線程語言最大的特性就...
摘要:上代碼代碼可以看出,不僅函數(shù)比指定的回調(diào)函數(shù)先執(zhí)行,而且函數(shù)也比先執(zhí)行。這是因?yàn)楹笠粋€事件進(jìn)入的時候,事件環(huán)可能處于不同的階段導(dǎo)致結(jié)果的不確定。這是因?yàn)橐驗(yàn)閳?zhí)行完后,程序設(shè)定了和,因此階段不會被阻塞進(jìn)而進(jìn)入階段先執(zhí)行,后進(jìn)入階段執(zhí)行。 JavaScript(簡稱JS)是前端的首要研究語言,要想真正理解JavaScript就繞不開他的運(yùn)行機(jī)制--Event Loop(事件環(huán)) JS是一門...
摘要:深入理解引擎的執(zhí)行機(jī)制靈魂三問為什么是單線程的為什么需要異步單線程又是如何實(shí)現(xiàn)異步的呢中的中的說說首先請牢記點(diǎn)是單線程語言的是的執(zhí)行機(jī)制。 深入理解JS引擎的執(zhí)行機(jī)制 1.靈魂三問 : JS為什么是單線程的? 為什么需要異步? 單線程又是如何實(shí)現(xiàn)異步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.說說setTimeout 首先,請牢記2...
摘要:博客地址是目錄區(qū)分進(jìn)程和線程在系統(tǒng)的任務(wù)管理器中可以查看當(dāng)前正在運(yùn)行的各種進(jìn)程。瀏覽器是多進(jìn)程的打開的任務(wù)管理器,可以看到當(dāng)前瀏覽器里的進(jìn)程。 在網(wǎng)上發(fā)現(xiàn)了一篇很好的博客文章,對瀏覽器進(jìn)程線程、Web Workers、Event Loop 等都解釋得通俗易懂。在此,我根據(jù)其內(nèi)容做了幾張思維導(dǎo)圖,對照著文章看可加深理解。如有更好的理解,歡迎探討。 博客地址是: http://www.da...
摘要:引擎線程,也稱為內(nèi)核,負(fù)責(zé)處理腳本程序,例如引擎。異步請求線程,也就是發(fā)出請求后,接收響應(yīng)檢測狀態(tài)變更等都是這個線程管理的。為了解決這個問題,提出標(biāo)準(zhǔn),允許腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作。 本文主要參閱了以下兩篇文章,對JS的Event Loop運(yùn)行機(jī)制基礎(chǔ)知識進(jìn)行了整理。從瀏覽器多進(jìn)程到JS單線程,JS運(yùn)行機(jī)制最全面的一次梳理JavaScript 運(yùn)行機(jī)制詳...
閱讀 2060·2021-11-23 09:51
閱讀 3353·2021-09-28 09:36
閱讀 1120·2021-09-08 09:35
閱讀 1758·2021-07-23 10:23
閱讀 3258·2019-08-30 15:54
閱讀 2998·2019-08-29 17:05
閱讀 438·2019-08-29 13:23
閱讀 1294·2019-08-28 17:51