您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Javascript的事件循環(huán)機制是什么”,內(nèi)容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“Javascript的事件循環(huán)機制是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
JavaScript是一種單線程語言,它主要用來與用戶互動,以及操作DOM。多線程需要共享資源、且有可能修改彼此的運行結果,且存在上下文切換。
在 JS 運行的時候可能會阻止 UI 渲染,這說明兩個線程是互斥的。這是因為 JS 可以修改 DOM,如果在 JS 執(zhí)行的時候 UI 線程還在工作,就可能導致不能安全的渲染 UI。
JS 是單線程運行的,可以達到節(jié)省內(nèi)存,節(jié)約上下文切換時間。
為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。
單線程的同步等待極大影響效率,任務不得不一個一個等待執(zhí)行,對于網(wǎng)頁應用是無法接受的。所以Javascript使用事件循環(huán)機制來解決異步任務的問題。
首先了解下同步和異步的區(qū)別:
同步:在一個函數(shù)返回的時候,調(diào)用者就能夠得到預期結果。
同步任務:在主線程上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務。
異步:在函數(shù)返回的時候,調(diào)用者還不能夠得到預期結果,而是需要在將來通過一定的手段得到。
異步任務:不進入主線程、而放在"任務隊列"中的任務,若有多個異步任務,則需排隊等待進入主線程執(zhí)行棧中被執(zhí)行。
任務隊列其實不止一種,根據(jù)任務種類的不同,可以分為微任務(micro task)隊列和宏任務(macro task)隊列。常見的任務如下:
宏任務:script(整體代碼)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate;需要特定的異步線程去執(zhí)行,有明確的異步任務去執(zhí)行,有回調(diào)。
微任務:Promise、MutaionObserver、process.nextTick(Node.js 環(huán)境,會先于其他微任務執(zhí)行);不需要特定的異步線程去執(zhí)行,沒有明確的異步任務去執(zhí)行,只有回調(diào)。
一次 Eventloop 循環(huán)會處理一個宏任務和所有這次循環(huán)中產(chǎn)生的微任務。 執(zhí)行順序如下圖:
第一個例子:
var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function (){}; req.onerror = function (){}; req.send(); //等同于 var req = new XMLHttpRequest(); req.open('GET', url); req.send(); req.onload = function (){}; req.onerror = function (){};
上面代碼中的req.send方法是Ajax操作向服務器發(fā)送數(shù)據(jù),它是一個異步任務,意味著只有當前腳本的所有代碼執(zhí)行完,系統(tǒng)才會去讀取"任務隊列"。指定回調(diào)函數(shù)的部分(onload和onerror),在send()方法的前面或后面無關緊要,因為它們屬于執(zhí)行棧的一部分,系統(tǒng)總是執(zhí)行完它們,才會去讀取"任務隊列"。
第二個例子:
console.log('1 第一次循環(huán) 開始執(zhí)行'); setTimeout(function () { console.log('2 第二次循環(huán) 開始執(zhí)行'); new Promise(function (resolve) { console.log('3 第二次循環(huán) 宏任務結束'); resolve(); }).then(function () { console.log('4 第二次循環(huán) 微任務執(zhí)行') }) }, 0) new Promise(function (resolve) { console.log('5 第一次循環(huán) 宏任務結束'); resolve(); }).then(function () { console.log('6 第一次循環(huán) 微任務執(zhí)行') }) setTimeout(function () { console.log('7 第三次循環(huán) 開始執(zhí)行'); new Promise(function (resolve) { console.log('8 第三次循環(huán) 宏任務結束'); resolve(); }).then(function () { console.log('9 第三次循環(huán) 微任務執(zhí)行') }) }, 0) /* 結果 1 第一次循環(huán) 開始執(zhí)行 5 第一次循環(huán) 宏任務結束 6 第一次循環(huán) 微任務執(zhí)行 2 第二次循環(huán) 開始執(zhí)行 3 第二次循環(huán) 宏任務結束 4 第二次循環(huán) 微任務執(zhí)行 7 第三次循環(huán) 開始執(zhí)行 8 第三次循環(huán) 宏任務結束 9 第三次循環(huán) 微任務執(zhí)行 */
定時器功能主要由setTimeout()和setInterval()這兩個函數(shù)來完成,它們的內(nèi)部運行機制完全一樣,區(qū)別在于前者指定的代碼是一次性執(zhí)行,后者則為反復執(zhí)行。
如果將setTimeout()的第二個參數(shù)設為0,就表示當前代碼執(zhí)行完(執(zhí)行棧清空)以后,立即執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)。主線程盡可能早得執(zhí)行,但是沒有辦法保證回調(diào)函數(shù)一定會在setTimeout()指定的時間執(zhí)行,因為必須等到當前代碼(執(zhí)行棧)執(zhí)行完,主線程才會去執(zhí)行它指定的回調(diào)函數(shù)。所以單線程無法實現(xiàn)真正的異步,因為還是存在阻塞。
HTML5標準規(guī)定了setTimeout()的第二個參數(shù)的最小值(最短間隔),不得低于4毫秒,如果低于這個值,就會自動增加。
在此之前,老版本的瀏覽器都將最短間隔設為10毫秒。
另外,對于那些DOM的變動(尤其是涉及頁面重新渲染的部分),通常不會立即執(zhí)行,而是每16毫秒執(zhí)行一次。
這時使用requestAnimationFrame()的效果要好于setTimeout()。
在Node.js環(huán)境下,還提供了另外兩個方法:
process.nextTick方法可以在當前"執(zhí)行棧"的尾部,下一次Event Loop之前,觸發(fā)回調(diào)函數(shù)。也就是說,它指定的任務總是在本次"事件循環(huán)"觸發(fā),發(fā)生在所有異步任務之前,同時也是在所有微任務之前執(zhí)行。
setImmediate方法則是在當前"任務隊列"的尾部添加事件,也就是說,它指定的任務總是在之后的Event Loop執(zhí)行,這與setTimeout(fn, 0)很像。
多個process.nextTick語句總是在當前"執(zhí)行棧"一次執(zhí)行完,多個setImmediate則可能需要多次loop才能執(zhí)行完。
Node.js使用V8作為js的解析引擎,而I/O處理方面使用了自己設計的libuv,libuv是一個基于事件驅動的跨平臺抽象層,封裝了不同操作系統(tǒng)一些底層特性,對外提供統(tǒng)一的API,事件循環(huán)機制也是它里面的實現(xiàn)的。(在Python中,uvloop,一個完整的asyncio事件循環(huán)的替代品,也是建立在libuv基礎之上,是由Cython編寫而成。)這個機制和瀏覽器中Javascript的事件循環(huán)機制是不太一樣的。
讀到這里,這篇“Javascript的事件循環(huán)機制是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內(nèi)容的文章,歡迎關注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。