摘要:這種問題在設(shè)置倒計時的經(jīng)常遇到,倒計時開始的時候設(shè)置的時間是從服務(wù)器拿到的系統(tǒng)時間很準(zhǔn)確,但是如果后面不定期像服務(wù)期請求系統(tǒng)時間進行校準(zhǔn)的話,你可能會發(fā)現(xiàn)倒計時的偏差越來越來大,這就是主線程執(zhí)行的時間比設(shè)定的延遲時間長導(dǎo)致的。
關(guān)于js執(zhí)行機制,老早之前就一直想寫篇文章做個總結(jié),因為和js執(zhí)行順序的面試題碰到的特別多,每次碰到總是會去網(wǎng)上查,沒有系統(tǒng)地總結(jié),搞得每次碰到都是似懂非懂的感覺,這篇文章就系統(tǒng)的總結(jié)一下js執(zhí)行機制。
任務(wù)隊列大家都知道js最大的特點就是單線程執(zhí)行,這就是為什么js簡單易學(xué)的一個重要原因,不需要考慮復(fù)雜的同步問題,但是單線程也會有一個問題,所有的任務(wù)在執(zhí)行的過程中都必須等待前一個任務(wù)執(zhí)行完成才能執(zhí)行,這樣就會帶來一個效率的問題,為了解決這個問題,js將任務(wù)分為兩種:同步任務(wù)和異步任務(wù),同步任務(wù)就是之前說后一個任務(wù)必須等待前一個任務(wù)執(zhí)行完成才能執(zhí)行,是在主線程上執(zhí)行的,而異步任務(wù)不會直接進入主線程執(zhí)行,而是進入任務(wù)隊列,只有在任務(wù)隊列通知異步任務(wù)可以執(zhí)行時,才會被推入主線程執(zhí)行。讓我們來看一個更加直觀的流程圖:
setTimeout和setInterval說到異步任務(wù),最常見就是setTimeout和setInterval兩兄弟了,setTimeout是延遲一定時間后執(zhí)行,但是只執(zhí)行一次,setInterval是每隔一定的時間執(zhí)行一次,會執(zhí)行多次,但是有時候我們會發(fā)現(xiàn)設(shè)置一定的延遲時間后,回調(diào)函數(shù)的執(zhí)行時間會比我們設(shè)置的時間要晚,這是為什么呢?上面我們說過,在任務(wù)執(zhí)行的時候setTimeout這類異步任務(wù)的回調(diào)會被放到異步隊列中等待執(zhí)行,當(dāng)延遲時間結(jié)束時,如果主線程的任務(wù)已經(jīng)執(zhí)行完了,也就是處在空閑狀態(tài)時,就會將任務(wù)隊列的回調(diào)推到主線程執(zhí)行,但是當(dāng)主線程的任務(wù)還沒有執(zhí)行完成時,就只能繼續(xù)等待,來看一個例子:
let before = new Date() setTimeout(() => { console.log(new Date() - before) }, 1000) for (let i = 0; i < 300000; i++) { console.log("time delay") }
從上面的例子就可以看到:當(dāng)我們執(zhí)行完setTimeout之后,立刻執(zhí)行20萬次的循環(huán),從執(zhí)行結(jié)果可以看到,setTimeout回調(diào)函數(shù)中的時間遠高于設(shè)置1000ms,這就是因為時間到了,但是主線程的任務(wù)還沒有執(zhí)行完成導(dǎo)致。這種問題在setInterval設(shè)置倒計時的經(jīng)常遇到,倒計時開始的時候設(shè)置的時間是從服務(wù)器拿到的系統(tǒng)時間很準(zhǔn)確,但是如果后面不定期像服務(wù)期請求系統(tǒng)時間進行校準(zhǔn)的話,你可能會發(fā)現(xiàn)倒計時的偏差越來越來大,這就是主線程執(zhí)行的時間比設(shè)定的延遲時間長導(dǎo)致的。
macrotask和microtask在js中,異步任務(wù)除了有setTimeout這類的異步任務(wù),還有一類就是es6中很常用promise...then這類的異步任務(wù),因此除了同步任務(wù)和異步任務(wù),任務(wù)還可以更加細分為macrotask(宏任務(wù))和microtask(微任務(wù))
macrotask: 包括setTimeout、setInterval和執(zhí)行棧
microtask: 包括Promise、process.nextTick
要想理解這兩個概念,直接從一道簡單的面試題入手,來看一個例子:
setTimeout(function() { console.log(1) }, 0); new Promise(function(resolve, reject) { console.log(2); resolve() }).then(function() { console.log(3) }); process.nextTick(function () { console.log(4) }) console.log(5)
思考一下上面例子的輸出結(jié)果,我們來仔細分析一下執(zhí)行過程:
第一輪:主線程開始執(zhí)行,遇到setTimeout,將setTimeout的回調(diào)函數(shù)丟到宏任務(wù)隊列中,在往下執(zhí)行new Promise立即執(zhí)行,輸出2,then的回調(diào)函數(shù)丟到微任務(wù)隊列中,再繼續(xù)執(zhí)行,遇到process.nextTick,同樣將回調(diào)函數(shù)扔到為任務(wù)隊列,再繼續(xù)執(zhí)行,輸出5,當(dāng)所有宏任務(wù)執(zhí)行完成后看有沒有可以執(zhí)行的微任務(wù),發(fā)現(xiàn)有then函數(shù)和nextTick兩個微任務(wù),先執(zhí)行哪個呢?process.nextTick指定的異步任務(wù)總是發(fā)生在所有異步任務(wù)之前,因此先執(zhí)行process.nextTick輸出4然后執(zhí)行then函數(shù)輸出3,第一輪執(zhí)行結(jié)束。
第二輪從宏任務(wù)隊列開始,發(fā)現(xiàn)setTimeout回調(diào),輸出1執(zhí)行完畢,因此結(jié)果是25431
最后用一張圖來總結(jié)一下:
總結(jié)這篇文章簡單介紹了js執(zhí)行機制,希望看了之后,可以對大家認(rèn)識js的執(zhí)行機制會有所幫助。
如果有錯誤或不嚴(yán)謹(jǐn)?shù)牡胤剑瑲g迎批評指正,如果喜歡,歡迎點贊收藏
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/100076.html
摘要:事件循環(huán)背景是一門單線程非阻塞的腳本語言,單線程意味著,代碼在執(zhí)行的任何時候,都只有一個主線程來處理所有的任務(wù)。在意識到該問題之際,新特性中的可以讓成為一門多線程語言,但實際開發(fā)中使用存在著諸多限制。這個地方被稱為執(zhí)行棧。 事件循環(huán)(Event Loop) 背景 JavaScript是一門單線程非阻塞的腳本語言,單線程意味著,JavaScript代碼在執(zhí)行的任何時候,都只有一個主線程來...
摘要:以多線程的形式,允許單個任務(wù)分成不同的部分進行運行。提供協(xié)調(diào)機制,一方面防止進程之間和線程之間產(chǎn)生沖突,另一方面允許進程之間和線程之間共享資源。主線程會不斷的重復(fù)上訴過程。 眾所周知,js是單線程的,說到線程,我們首先來仔細辨析一下線程和進程的知識。 一、進程與線程 阮一峰老師的一篇文章寫的很好 cpu會給當(dāng)前進程分配資源,進程是資源分配的最小單位,進程的資源會分配給線程使用,線程是C...
摘要:如果沒有其他異步任務(wù)要處理比如到期的定時器,會一直停留在這個階段,等待請求返回結(jié)果。執(zhí)行的執(zhí)行事件關(guān)閉請求的,例如事件循環(huán)的每一次循環(huán)都需要依次經(jīng)過上述的階段。因此,才會早于執(zhí)行。 showImg(https://segmentfault.com/img/bVbnY76); 概念 同步任務(wù)(Synchronous) 在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù) ...
閱讀 1948·2021-11-22 14:44
閱讀 1682·2021-11-02 14:46
閱讀 3674·2021-10-13 09:40
閱讀 2609·2021-09-07 09:58
閱讀 1627·2021-09-03 10:28
閱讀 1669·2019-08-29 15:30
閱讀 987·2019-08-29 15:28
閱讀 1477·2019-08-26 12:20