摘要:上代碼代碼可以看出,不僅函數(shù)比指定的回調(diào)函數(shù)先執(zhí)行,而且函數(shù)也比先執(zhí)行。這是因?yàn)楹笠粋€(gè)事件進(jìn)入的時(shí)候,事件環(huán)可能處于不同的階段導(dǎo)致結(jié)果的不確定。這是因?yàn)橐驗(yàn)閳?zhí)行完后,程序設(shè)定了和,因此階段不會(huì)被阻塞進(jìn)而進(jìn)入階段先執(zhí)行,后進(jìn)入階段執(zhí)行。
JavaScript(簡稱JS)是前端的首要研究語言,要想真正理解JavaScript就繞不開他的運(yùn)行機(jī)制--Event Loop(事件環(huán))
JS是一門單線程的語言,異步操作是實(shí)際應(yīng)用中的重要的一部分,關(guān)于異步操作參考我的另一篇文章js異步發(fā)展歷史與Promise原理分析 這里不再贅述。
堆、棧、隊(duì)列 堆(heap)堆(heap)是指程序運(yùn)行時(shí)申請(qǐng)的動(dòng)態(tài)內(nèi)存,在JS運(yùn)行時(shí)用來存放對(duì)象。
棧(stack)棧(stack)遵循的原則是“先進(jìn)后出”,JS種的基本數(shù)據(jù)類型與指向?qū)ο蟮牡刂反娣旁跅?nèi)存中,此外還有一塊棧內(nèi)存用來執(zhí)行JS主線程--執(zhí)行棧(execution context stack),此文章中的棧只考慮執(zhí)行棧。
隊(duì)列(queue)隊(duì)列(queue)遵循的原則是“先進(jìn)先出”,JS中除了主線程之外還存在一個(gè)“任務(wù)隊(duì)列”(其實(shí)有兩個(gè),后面再詳細(xì)說明)。
Event LoopJS的單線程也就是說所有的任務(wù)都需要按照一定的規(guī)則順序排隊(duì)執(zhí)行,這個(gè)規(guī)則就是我們要說明的Event Loop事件環(huán)。Event Loop在不同的運(yùn)行環(huán)境下有著不同的方式。
瀏覽器環(huán)境下的Event Loop先上圖(轉(zhuǎn)自Philip Roberts的演講《Help, I"m stuck in an event-loop》)
當(dāng)主線程運(yùn)行的時(shí)候,JS會(huì)產(chǎn)生堆和棧(執(zhí)行棧)
主線程中調(diào)用的webaip所產(chǎn)生的異步操作(dom事件、ajax回調(diào)、定時(shí)器等)只要產(chǎn)生結(jié)果,就把這個(gè)回調(diào)塞進(jìn)“任務(wù)隊(duì)列”中等待執(zhí)行。
當(dāng)主線程中的同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)依次讀取“任務(wù)隊(duì)列”中的任務(wù),將任務(wù)放進(jìn)執(zhí)行棧中執(zhí)行。
執(zhí)行任務(wù)時(shí)可能還會(huì)產(chǎn)生新的異步操作,會(huì)產(chǎn)生新的循環(huán),整個(gè)過程是循環(huán)不斷的。
從事件環(huán)中不難看出當(dāng)我們調(diào)用setTimeout并設(shè)定一個(gè)確定的時(shí)間,而這個(gè)任務(wù)的實(shí)際執(zhí)行時(shí)間可能會(huì)由于主線程中的任務(wù)沒有執(zhí)行完而大于我們?cè)O(shè)定的時(shí)間,導(dǎo)致定時(shí)器不準(zhǔn)確,也是連續(xù)調(diào)用setTimeout與調(diào)用setInterval會(huì)產(chǎn)生不同效果的原因(此處就不再展開,有時(shí)間我會(huì)多帶帶寫一篇文章)。
接下來上代碼:
console.log(1); console.log(2); setTimeout(function(){ console.log(3) setTimeout(function(){ console.log(6); }) },0) setTimeout(function(){ console.log(4); setTimeout(function(){ console.log(7); }) },0) console.log(5)
代碼中的setTimeout的時(shí)間給得0,相當(dāng)于4ms,也有可能大于4ms(不重要)。我們要注意的是代碼輸出的順序。我們把任務(wù)以其輸出的數(shù)字命名。
先執(zhí)行的一定是同步代碼,先輸出1,2,5,而3任務(wù),4任務(wù)這時(shí)會(huì)依次進(jìn)入“任務(wù)隊(duì)列中”。同步代碼執(zhí)行完畢,隊(duì)列中的3會(huì)進(jìn)入執(zhí)行棧執(zhí)行,4到了隊(duì)列的最前端,3執(zhí)行完后,內(nèi)部的setTimeout將6的任務(wù)放入隊(duì)列尾部。開始執(zhí)行4任務(wù)……
最終我們得到的輸出為1,2,5,3,4,6,7。
宏任務(wù)與微任務(wù)任務(wù)隊(duì)列中的所有任務(wù)都是會(huì)乖乖排隊(duì)的嗎?答案是否定的,任務(wù)也是有區(qū)別的,總是有任務(wù)會(huì)有一些特權(quán)(比如插隊(duì)),就是任務(wù)中的vip--微任務(wù)(micro-task),那些沒有特權(quán)的--宏任務(wù)(macro-task)。
我們看一段代碼:
console.log(1); setTimeout(function(){ console.log(2); Promise.resolve(1).then(function(){ console.log("promise") }) }) setTimeout(function(){ console.log(3); })
按照“隊(duì)列理論”,結(jié)果應(yīng)該為1,2,3,promise??墒菍?shí)際結(jié)果事與愿違輸出的是1,2,promise,3。
明明是3先進(jìn)入的隊(duì)列 ,為什么promise會(huì)排在前面輸出?這是因?yàn)閜romise有特權(quán)是微任務(wù),當(dāng)主線程任務(wù)執(zhí)行完畢微任務(wù)會(huì)排在宏任務(wù)前面先去執(zhí)行,不管是不是后來的。
換句話說,就是任務(wù)隊(duì)列實(shí)際上有兩個(gè),一個(gè)是宏任務(wù)隊(duì)列,一個(gè)是微任務(wù)隊(duì)列,當(dāng)主線程執(zhí)行完畢,如果微任務(wù)隊(duì)列中有微任務(wù),則會(huì)先進(jìn)入執(zhí)行棧,當(dāng)微任務(wù)隊(duì)列沒有任務(wù)時(shí),才會(huì)執(zhí)行宏任務(wù)的隊(duì)列。
微任務(wù)包括: 原生Promise(有些實(shí)現(xiàn)的promise將then方法放到了宏任務(wù)中),Object.observe(已廢棄), MutationObserver, MessageChannel;
宏任務(wù)包括:setTimeout, setInterval, setImmediate, I/O;
Node環(huán)境下的Event Loop┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
node中的時(shí)間循環(huán)與瀏覽器的不太一樣,如圖:
timers 階段: 這個(gè)階段執(zhí)行setTimeout(callback) and setInterval(callback)預(yù)定的callback;
I/O callbacks 階段: 執(zhí)行除了close事件的callbacks、被timers(定時(shí)器,setTimeout、setInterval等)設(shè)定的callbacks、setImmediate()設(shè)定的callbacks之外的callbacks;
idle, prepare 階段: 僅node內(nèi)部使用;
poll 階段: 獲取新的I/O事件, 適當(dāng)?shù)臈l件下node將阻塞在這里;
check 階段: 執(zhí)行setImmediate() 設(shè)定的callbacks;
close callbacks 階段: 比如socket.on(‘close’, callback)的callback會(huì)在這個(gè)階段執(zhí)行。
每一個(gè)階段都有一個(gè)裝有callbacks的fifo queue(隊(duì)列),當(dāng)event loop運(yùn)行到一個(gè)指定階段時(shí),
node將執(zhí)行該階段的fifo queue(隊(duì)列),當(dāng)隊(duì)列callback執(zhí)行完或者執(zhí)行callbacks數(shù)量超過該階段的上限時(shí),
event loop會(huì)轉(zhuǎn)入下一下階段。
process.nextTick方法不在上面的事件環(huán)中,我們可以把它理解為微任務(wù),它的執(zhí)行時(shí)機(jī)是當(dāng)前"執(zhí)行棧"的尾部----下一次Event Loop(主線程讀取"任務(wù)隊(duì)列")之前----觸發(fā)回調(diào)函數(shù)。也就是說,它指定的任務(wù)總是發(fā)生在所有異步任務(wù)之前。setImmediate方法則是在當(dāng)前"任務(wù)隊(duì)列"的尾部添加事件,也就是說,它指定的任務(wù)總是在下一次Event Loop時(shí)執(zhí)行。上代碼:
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log("TIMEOUT FIRED"); }, 0) // 1 // 2 // TIMEOUT FIRED
代碼可以看出,不僅函數(shù)A比setTimeout指定的回調(diào)函數(shù)timeout先執(zhí)行,而且函數(shù)B也比timeout先執(zhí)行。這說明,如果有多個(gè)process.nextTick語句(不管它們是否嵌套),將全部在當(dāng)前"執(zhí)行棧"執(zhí)行。
setTimeout 和 setImmediate二者非常相似,但是二者區(qū)別取決于他們什么時(shí)候被調(diào)用.
setImmediate 設(shè)計(jì)在poll階段完成時(shí)執(zhí)行,即check階段;
setTimeout 設(shè)計(jì)在poll階段為空閑時(shí),且設(shè)定時(shí)間到達(dá)后執(zhí)行;但其在timer階段執(zhí)行
其二者的調(diào)用順序取決于當(dāng)前event loop的上下文,如果他們?cè)诋惒絠/o callback之外調(diào)用,其執(zhí)行先后順序是不確定的。
setTimeout(function timeout () { console.log("timeout"); },0); setImmediate(function immediate () { console.log("immediate"); });
$ node timeout_vs_immediate.js timeout immediate $ node timeout_vs_immediate.js immediate timeout
這是因?yàn)楹笠粋€(gè)事件進(jìn)入的時(shí)候,事件環(huán)可能處于不同的階段導(dǎo)致結(jié)果的不確定。當(dāng)我們給了事件環(huán)確定的上下文,事件的先后就能確定了。
var fs = require("fs") fs.readFile(__filename, () => { setTimeout(() => { console.log("timeout") }, 0) setImmediate(() => { console.log("immediate") }) })
$ node timeout_vs_immediate.js immediate timeout
這是因?yàn)橐驗(yàn)閒s.readFile callback執(zhí)行完后,程序設(shè)定了timer 和 setImmediate,因此poll階段不會(huì)被阻塞進(jìn)而進(jìn)入check階段先執(zhí)行setImmediate,后進(jìn)入timer階段執(zhí)行setTimeout。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93496.html
摘要:主線程在任務(wù)隊(duì)列中讀取事件,這個(gè)過程是循環(huán)不斷地,所以這種運(yùn)行機(jī)制叫做事件循環(huán)是在執(zhí)行棧同步代碼結(jié)束之后,下一次任務(wù)隊(duì)列執(zhí)行之前。 單線程 javascript為什么是單線程語言,原因在于如果是多線程,當(dāng)一個(gè)線程對(duì)DOM節(jié)點(diǎn)做添加內(nèi)容操作的時(shí)候,另一個(gè)線程要?jiǎng)h除這個(gè)DOM節(jié)點(diǎn),這個(gè)時(shí)候,瀏覽器應(yīng)該怎么選擇,這就造成了混亂,為了解決這類問題,在一開始的時(shí)候,javascript就采用單線...
摘要:中線程運(yùn)行機(jī)制詳解對(duì)于我們都知道,他是個(gè)單線程語言,但是準(zhǔn)確來說它是擁有一個(gè)執(zhí)行程序主線程,和消息隊(duì)列輔線程,以及各個(gè)真正處理異步操作的工作線程。 JavaScript中線程運(yùn)行機(jī)制詳解 對(duì)于JavaScript我們都知道,他是個(gè)單線程語言,但是準(zhǔn)確來說它是擁有一個(gè)執(zhí)行程序主線程,和消息隊(duì)列輔線程(Event Loop),以及各個(gè)真正處理異步操作的工作線程。當(dāng)主線程執(zhí)行JS程序的時(shí)候,...
摘要:機(jī)制詳解與中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代開發(fā)語法基礎(chǔ)與實(shí)踐技巧系列文章。事件循環(huán)機(jī)制詳解與實(shí)踐應(yīng)用是典型的單線程單并發(fā)語言,即表示在同一時(shí)間片內(nèi)其只能執(zhí)行單個(gè)任務(wù)或者部分代碼片。 JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代 JavaScript 開發(fā):語法基礎(chǔ)與實(shí)踐技巧系列文章。本文依次介紹了函數(shù)調(diào)用棧、MacroTask 與 Micr...
摘要:曾經(jīng)的理解首先,是單線程語言,也就意味著同一個(gè)時(shí)間只能做一件事,那么為什么不是多線程呢這樣還能提高效率啊假定同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè)節(jié)點(diǎn)上編輯了內(nèi)容,而另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器就很懵逼了,到底以執(zhí)行哪個(gè)操作呢所以,設(shè)計(jì)者把 Event Loop曾經(jīng)的理解 首先,JS是單線程語言,也就意味著同一個(gè)時(shí)間只能做一件事,那么 為什么JavaScript不是多線程呢?這樣還能提...
摘要:主線程要明確的一點(diǎn)是,主線程跟執(zhí)行棧是不同概念,主線程規(guī)定現(xiàn)在執(zhí)行執(zhí)行棧中的哪個(gè)事件。主線程循環(huán)即主線程會(huì)不停的從執(zhí)行棧中讀取事件,會(huì)執(zhí)行完所有棧中的同步代碼。以上參考資料詳解中的事件循環(huán)機(jī)制中的事件循環(huán)運(yùn)行機(jī)制詳解再談 showImg(https://segmentfault.com/img/remote/1460000015317437?w=1920&h=1080); 前言 大家都...
閱讀 3337·2021-11-22 12:04
閱讀 2721·2019-08-29 13:49
閱讀 493·2019-08-26 13:45
閱讀 2256·2019-08-26 11:56
閱讀 1012·2019-08-26 11:43
閱讀 605·2019-08-26 10:45
閱讀 1279·2019-08-23 16:48
閱讀 2167·2019-08-23 16:07