摘要:換句話說(shuō)當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者不會(huì)立刻得到結(jié)果,而是調(diào)用發(fā)出后,被調(diào)用者通過(guò)狀態(tài)通知或回調(diào)函數(shù)處理這個(gè)調(diào)用。
JavaScript單線程機(jī)制
任務(wù)隊(duì)列JavaScript的一個(gè)語(yǔ)言特性(也是這門(mén)語(yǔ)言的核心)就是單線程。什么是單線程呢?簡(jiǎn)單地說(shuō)就是同一時(shí)間只能做一件事,當(dāng)有多個(gè)任務(wù)時(shí),只能按照一個(gè)順序一個(gè)完成了再執(zhí)行下一個(gè)
為什么JS是單線程的呢?
JS最初被設(shè)計(jì)用在瀏覽器中,作為瀏覽器腳本語(yǔ)言,JavaScript的主要用途是與用戶(hù)互動(dòng),以及操作DOM
如果瀏覽器中的JS是多線程的,會(huì)帶來(lái)很復(fù)雜的同步問(wèn)題
比如,假定JavaScript同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)?
所以為了避免復(fù)雜性,JavaScript從誕生起就是單線程
為了提高CPU的利用率,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以這個(gè)標(biāo)準(zhǔn)并沒(méi)有改變JavaScript單線程的本質(zhì)
Event Loop同步和異步
同步和異步關(guān)注的是消息通知機(jī)制同步:發(fā)出調(diào)用后,沒(méi)有得到結(jié)果之前,該調(diào)用不返回,一旦調(diào)用返回,就得到返回值了。 簡(jiǎn)而言之就是調(diào)用者主動(dòng)等待這個(gè)調(diào)用的結(jié)果
異步:調(diào)用者在發(fā)出調(diào)用后這個(gè)調(diào)用就直接返回了,所以沒(méi)有返回結(jié)果。換句話說(shuō)當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者不會(huì)立刻得到結(jié)果,而是調(diào)用發(fā)出后,被調(diào)用者通過(guò)狀態(tài)、通知或回調(diào)函數(shù)處理這個(gè)調(diào)用。
阻塞和非阻塞
阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息,返回值)時(shí)的狀態(tài)阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起。調(diào)用線程只有在得到結(jié)果之后才會(huì)返回
非阻塞調(diào)用指在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程
單線程意味著同一時(shí)間只能進(jìn)行一件事情,前面的事情結(jié)束才能執(zhí)行后面的事件.當(dāng)碰到需要時(shí)間的IO事件的時(shí)候問(wèn)題就來(lái)了,必須等到這些結(jié)束后才往下進(jìn)行,但這時(shí)CPU是閑著的.這樣浪費(fèi)了很多計(jì)算機(jī)的性能.
JavaScript語(yǔ)言的設(shè)計(jì)者意識(shí)到,這時(shí)主線程完全可以不管IO設(shè)備,掛起處于等待中的任務(wù),先運(yùn)行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果,再回過(guò)頭,把掛起的任務(wù)繼續(xù)執(zhí)行下去.
于是,所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);異步任務(wù)指的是,不進(jìn)入主線程、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。
(1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack)
(2)主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列",看看里面有哪些事件。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開(kāi)始執(zhí)行
(4)主線程不斷重復(fù)上面的第三步
主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱(chēng)為Event Loop(事件循環(huán))上圖中,主線程運(yùn)行的時(shí)候,產(chǎn)生堆(heap)和棧(stack),堆中可存放對(duì)象, 棧中可存放變量,函數(shù),函數(shù)指針,代碼語(yǔ)句等
棧中的代碼調(diào)用各種外部API,它們?cè)?任務(wù)隊(duì)列"中加入各種事件(click,load,done)
WebAPIs都是多帶帶線程,跟組件中的不一樣,不會(huì)阻塞主線程執(zhí)行,比如獲取后臺(tái)數(shù)據(jù),若同步就阻塞了,比如HTTP請(qǐng)求又開(kāi)辟了一個(gè)線程當(dāng)執(zhí)行棧中的任務(wù)完成后,主線程會(huì)去讀取事件隊(duì)列(先進(jìn)先出),執(zhí)行相應(yīng)的回調(diào)函數(shù)
舉個(gè)例子,查看以下代碼
function read(){ console.log(1); setTimeout(function (){ console.log(2); setTimeout(function (){ console.log(4) }); }); setTimeout(function (){ console.log(5) }) console.log(3); } read();
代碼執(zhí)行結(jié)果:1 3 2 5 4定時(shí)器先執(zhí)行同步代碼打印1,3,setTimeout異步代碼放到事件隊(duì)列中,先放的先執(zhí)行,后放的后執(zhí)行
"任務(wù)隊(duì)列"可以放置定時(shí)事件,即指定某些代碼在多少時(shí)間之后執(zhí)行定時(shí)器功能主要由setTimeout()和setInterval()這兩個(gè)函數(shù)來(lái)完成,它們的內(nèi)部運(yùn)行機(jī)制完全一樣,區(qū)別在于前者指定的代碼是一次性執(zhí)行,后者則為反復(fù)執(zhí)行,主要以setTimeout舉例說(shuō)明
setTimeout()接受兩個(gè)參數(shù),第一個(gè)是回調(diào)函數(shù),第二個(gè)是推遲執(zhí)行的毫秒數(shù)
setTimeout(function () { console.log(3) }, 2000); setTimeout(function () { console.log(1); setTimeout(function () { console.log(2); }, 1000); }, 1000);
Promise與process.nextTick(callback)執(zhí)行結(jié)果是:1 3 2
setTimeout()將事件放到等待任務(wù)隊(duì)里中,當(dāng)主任務(wù)隊(duì)列的任務(wù)執(zhí)行完后,再執(zhí)行等待任務(wù)隊(duì)列,等待任務(wù)隊(duì)里中先返回的先執(zhí)行
setTimeout()有時(shí)候明明寫(xiě)的延時(shí)3秒,實(shí)際卻5,6秒才執(zhí)行函數(shù),這是怎么回事呢?
setTimeout()只是將事件插入了“任務(wù)隊(duì)列”,必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)。要是當(dāng)前代碼耗時(shí)很長(zhǎng),有可能要等很久,所以并沒(méi)有辦法保證回調(diào)函數(shù)一定會(huì)在setTimeout()指定的時(shí)間執(zhí)行
除了廣義的同步任務(wù)和異步任務(wù),我們對(duì)任務(wù)有更精細(xì)的定義:
macro-task(宏任務(wù)):包括整體代碼script,setTimeout,setInterval
micro-task(微任務(wù)):Promise,process.nextTick
- process.nextTick:在事件循環(huán)的下一次循環(huán)中調(diào)用 callback 回調(diào)函數(shù)。效果是將一個(gè)函數(shù)推遲到代碼書(shū)寫(xiě)的下一個(gè)同步方法執(zhí)行完畢時(shí)或異步方法的事件回調(diào)函數(shù)開(kāi)始執(zhí)行時(shí);與setTimeout(fn, 0) 函數(shù)的功能類(lèi)似,但它的效率高多了
不同類(lèi)型的任務(wù)會(huì)進(jìn)入對(duì)應(yīng)的Event Queue,比如 setTimeout 和 setInterval 會(huì)進(jìn)入相同的Event Queue
事件循環(huán)的順序,決定js代碼的執(zhí)行順序。進(jìn)入整體代碼(宏任務(wù))后,開(kāi)始第一次循環(huán)。接著執(zhí)行所有的微任務(wù)。然后再次從宏任務(wù)開(kāi)始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,再執(zhí)行所有的微任務(wù)。
事件循環(huán),宏任務(wù),微任務(wù)的關(guān)系如下所示:
宏任務(wù)=>執(zhí)行結(jié)束=>有可執(zhí)行的微任務(wù)=>執(zhí)行所有微任務(wù)=>開(kāi)始新的宏任務(wù)
宏任務(wù)=>執(zhí)行結(jié)束=>沒(méi)有可執(zhí)行的微任務(wù)=>開(kāi)始新的宏任務(wù)
我們用一段代碼說(shuō)明:
setTimeout(function () { console.log("setTimeout"); }); new Promise(function (resolve) { console.log("promise"); }).then(function () { console.log("then"); }); console.log("console");
這段代碼作為宏任務(wù),進(jìn)入主線程
先遇到 setTimeout ,那么將其回調(diào)函數(shù)注冊(cè)后分發(fā)到宏任務(wù)Event Queue
接下來(lái)遇到了 Promise , new Promise 立即執(zhí)行, then 函數(shù)分發(fā)到微任務(wù)Event Queue
遇到 console.log() ,立即執(zhí)行
-整體代碼script作為第一個(gè)宏任務(wù)執(zhí)行結(jié)束,看看有哪些微任務(wù)?我們發(fā)現(xiàn)了 then 在微任務(wù)Event Queue里面執(zhí)行
第一輪事件循環(huán)結(jié)束了,我們開(kāi)始第二輪循環(huán),當(dāng)然要從宏任務(wù)Event Queue開(kāi)始。我們發(fā)現(xiàn)了宏任務(wù)Event Queue中 setTimeout 對(duì)應(yīng)的回調(diào)函數(shù),立即執(zhí)行
結(jié)束
我們?cè)倏聪乱欢未a說(shuō)明:
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log("TIMEOUT FIRED"); }, 0)
以上代碼執(zhí)行結(jié)果:1 2 TIMEOUT FIRED上面代碼中,由于process.nextTick方法指定的回調(diào)函數(shù),總是在當(dāng)前"執(zhí)行棧"的尾部觸發(fā),所以不僅函數(shù)A比setTimeout指定的回調(diào)函數(shù)timeout先執(zhí)行,而且函數(shù)B也比timeout先執(zhí)行。這說(shuō)明,如果有多個(gè)process.nextTick語(yǔ)句(不管它們是否嵌套),將全部在當(dāng)前"執(zhí)行棧"執(zhí)行
我們?cè)倏聪乱欢未a說(shuō)明:
function a() { setTimeout(function () { console.log("a2"); }, 0); process.nextTick(function () { console.log("a1") }); } function b() { process.nextTick(function () { console.log("b1"); }) } a(); b();
一個(gè)函數(shù)執(zhí)行會(huì)形成一個(gè)執(zhí)行棧,任務(wù)隊(duì)列里的回調(diào)函數(shù)每次只取一個(gè),它執(zhí)行的時(shí)候會(huì)形成一個(gè)執(zhí)行棧,當(dāng)你第一次運(yùn)行這個(gè)腳本的時(shí)候,這個(gè)腳本的里所有的同步代碼都會(huì)在一個(gè)執(zhí)行棧里
a的執(zhí)行和b的執(zhí)行在一個(gè)執(zhí)行棧里,它們共同在第一個(gè)宏任務(wù)中
a執(zhí)行時(shí)候,會(huì)把a(bǔ)2放入宏任務(wù)隊(duì)列,把a(bǔ)1放入微任務(wù)隊(duì)列。
b執(zhí)行的時(shí)候,把b1放入微任務(wù)隊(duì)列
-------------------第一個(gè)宏任務(wù)執(zhí)行完畢-------------------------
宏任務(wù)執(zhí)行完畢后會(huì)把微任務(wù)隊(duì)列清空,也就是把a(bǔ)1 和b1都執(zhí)行,輸出a1和b1
-------------------第一個(gè)微任務(wù)隊(duì)列清空--------------------------
然后從宏任務(wù)隊(duì)列中取出下一個(gè)宏任務(wù),也就是a2執(zhí)行.輸出a2
為什么一個(gè)宏任務(wù)要搭配處理一個(gè)微任務(wù)
因?yàn)檫@樣最合理,微任務(wù)就是在有空時(shí)需要立即執(zhí)行的任務(wù),宏任務(wù)相比微任務(wù)可以滯后執(zhí)行。他們雖然都屬于異步任務(wù),但是通過(guò)這種優(yōu)先級(jí)的設(shè)置達(dá)到了控制異步回調(diào)執(zhí)行順序的目的。值得注意的是:同步代碼執(zhí)行完會(huì)先清空微任務(wù),然后取出宏任務(wù)隊(duì)列里的第一個(gè)事件對(duì)應(yīng)的回調(diào)到執(zhí)行棧執(zhí)行,然后再清空一次微任務(wù),如此循環(huán)...通過(guò)以上三段代碼,您是否對(duì)JS的執(zhí)行順序有所了解呢
我們來(lái)分析一段較復(fù)雜的代碼,看看你是否真的掌握了js的執(zhí)行機(jī)制
console.log("main1"); setTimeout(function () { console.log("setTimeout"); process.nextTick(function () { console.log("process.nextTick2"); }); }, 0); new Promise(function (resolve, reject) { console.log("promise"); resolve(); }).then(function () { console.log("promise then"); }); process.nextTick(function () { console.log("process.nextTick1"); }); console.log("main2");
以上代碼的執(zhí)行結(jié)果是:main1=>promise=>main2=>process.nextTick1=>promise then=>setTimeout=>process.nextTick2
系統(tǒng)啟動(dòng)執(zhí)行腳本,這個(gè)腳本就是一個(gè)宏任務(wù),執(zhí)行代碼塊中所有的同步代碼,輸出main1
next1放入微任務(wù),setTimeout+nextTick2(下一輪)放入宏任務(wù)隊(duì)列
promise構(gòu)造函數(shù)部分是同步的,立刻執(zhí)行輸出promise,promise then放入微任務(wù)
下面同步代碼輸出main2
接下來(lái)執(zhí)行微任務(wù)輸出nextTick1,promise then
接下來(lái)執(zhí)行宏任務(wù)輸出setTimeout,將nexttick2放入微任務(wù)隊(duì)列
接下來(lái)執(zhí)行微任務(wù)nexttick2
nextTick是由node自己定義并實(shí)現(xiàn)的概念,它的回調(diào)調(diào)用入口在event loop過(guò)程中MakeCallback函數(shù)的末尾,驅(qū)動(dòng)調(diào)用清空js層的queue,最后再執(zhí)行microtasks,適當(dāng)處理下可能觸發(fā)的promise,明顯 process.nextTick1> promise.then
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/92624.html
摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱(chēng)為事件循環(huán)。上面也提到,在到達(dá)指定時(shí)間時(shí),定時(shí)器就會(huì)將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時(shí)器功能。關(guān)于定時(shí)器的重要補(bǔ)充定時(shí)器包括與兩個(gè)方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...
摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱(chēng)為事件循環(huán)。上面也提到,在到達(dá)指定時(shí)間時(shí),定時(shí)器就會(huì)將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時(shí)器功能。關(guān)于定時(shí)器的重要補(bǔ)充定時(shí)器包括與兩個(gè)方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...
摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱(chēng)為事件循環(huán)。上面也提到,在到達(dá)指定時(shí)間時(shí),定時(shí)器就會(huì)將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時(shí)器功能。關(guān)于定時(shí)器的重要補(bǔ)充定時(shí)器包括與兩個(gè)方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...
摘要:的單線程,與它的用途有關(guān)。只要指定過(guò)回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入任務(wù)隊(duì)列,等待主線程讀取。四主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱(chēng)為事件循環(huán)。令人困惑的是,文檔中稱(chēng),指定的回調(diào)函數(shù),總是排在前面。 原文:http://www.cnblogs.com/Master... 一、為什么JavaScript是單線程? JavaScript語(yǔ)言的一大特點(diǎn)...
摘要:圖片轉(zhuǎn)引自的演講和兩個(gè)定時(shí)器中回調(diào)的執(zhí)行邏輯便是典型的機(jī)制。異步編程關(guān)于異步編程我的理解是,在執(zhí)行環(huán)境所提供的異步機(jī)制之上,在應(yīng)用編碼層面上實(shí)現(xiàn)整體流程控制的異步風(fēng)格。 問(wèn)題背景 在一次開(kāi)發(fā)任務(wù)中,需要實(shí)現(xiàn)如下一個(gè)餅狀圖動(dòng)畫(huà),基于canvas進(jìn)行繪圖,但由于對(duì)于JS運(yùn)行環(huán)境中異步機(jī)制的不了解,所以遇到了一個(gè)棘手的問(wèn)題,始終無(wú)法解決,之后在與同事交流之后才恍然大悟。問(wèn)題的根節(jié)在于經(jīng)典的J...
閱讀 3680·2021-11-24 10:25
閱讀 2677·2021-11-24 09:38
閱讀 1303·2021-09-08 10:41
閱讀 3074·2021-09-01 10:42
閱讀 2726·2021-07-25 21:37
閱讀 2057·2019-08-30 15:56
閱讀 975·2019-08-30 15:55
閱讀 2814·2019-08-30 15:54