JavaScript的單線程和事件循環(huán)是什么?針對(duì)這個(gè)問題,今天小編總結(jié)了這篇文章,希望能幫助更多想解決這個(gè)問題的朋友找到更加簡(jiǎn)單易行的辦法。
創(chuàng)新互聯(lián)專注于新樂網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供新樂營(yíng)銷型網(wǎng)站建設(shè),新樂網(wǎng)站制作、新樂網(wǎng)頁設(shè)計(jì)、新樂網(wǎng)站官網(wǎng)定制、成都微信小程序服務(wù),打造新樂網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供新樂網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
單線程就是進(jìn)程只有一個(gè)線程,線程較多線程來說,系統(tǒng)穩(wěn)定、擴(kuò)展性極強(qiáng)、軟件豐富。多用于點(diǎn)對(duì)點(diǎn)的服務(wù)。
"單線程"語言
在瀏覽器實(shí)現(xiàn)中,每個(gè)單頁都是一個(gè)獨(dú)立進(jìn)程,其中包含了JS引擎、GUI界面渲染、事件觸發(fā)、定時(shí)觸發(fā)器、異步HTTP請(qǐng)求等多個(gè)線程。
進(jìn)程(Process)是操作系統(tǒng)CPU等資源分配的最小單位,是程序的執(zhí)行實(shí)體,是線程的容器。
線程(Thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,一條線程指的是進(jìn)程中一個(gè)單一順序的控制流。
因此我們可以說JS是"單線程"式的語言,代碼只能按照單一順序進(jìn)行串行執(zhí)行,并在執(zhí)行完成前阻塞其他代碼。
JS數(shù)據(jù)結(jié)構(gòu)
如上圖所示為JS的幾種重要數(shù)據(jù)結(jié)構(gòu):
● 棧(Stack):用于JS的函數(shù)嵌套調(diào)用,后進(jìn)先出,直到棧被清空。
● 堆(Heap):用于存儲(chǔ)大塊數(shù)據(jù)的內(nèi)存區(qū)域,如對(duì)象。
● 隊(duì)列(Queue):用于事件循環(huán)機(jī)制,先進(jìn)先出,直到隊(duì)列為空。
事件循環(huán)
javascript是單線程的語言,也就是說,同一個(gè)時(shí)間只能做一件事。而這個(gè)單線程的特性,與它的用途有關(guān),作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動(dòng),以及操作DOM。這決定了它只能是單線程,否則會(huì)帶來很復(fù)雜的同步問題。比如,假定JavaScript同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)?
為了利用多核CPU的計(jì)算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒有改變JavaScript單線程的本質(zhì)
我們的經(jīng)驗(yàn)告訴我們JS是可以并發(fā)執(zhí)行的,比如定時(shí)任務(wù)、并發(fā)AJAX請(qǐng)求,那這些是怎么完成的呢?其實(shí)這些都是JS在用單線程模擬多線程完成的。
如上圖所示,JS串行執(zhí)行主線程任務(wù),當(dāng)遇到異步任務(wù)如定時(shí)器時(shí),將其放入事件隊(duì)列中,在主線程任務(wù)執(zhí)行完畢后,再去事件隊(duì)列中遍歷取出隊(duì)首任務(wù)進(jìn)行執(zhí)行,直至隊(duì)列為空。
全部執(zhí)行完成后,會(huì)有主監(jiān)控進(jìn)程,持續(xù)檢測(cè)隊(duì)列是否為空,如果不為空,則繼續(xù)事件循環(huán)。
setTimeout定時(shí)任務(wù)
定時(shí)任務(wù)setTimeout(fn, timeout)
會(huì)先被交給瀏覽器的定時(shí)器模塊,等延遲時(shí)間到了,再將事件放入到事件隊(duì)列里,等主線程執(zhí)行結(jié)束后,如果隊(duì)列中沒有其他任務(wù),則會(huì)被立即處理,而如果還有沒有執(zhí)行完成的任務(wù),則需要等前面的任務(wù)都執(zhí)行完成才會(huì)被執(zhí)行。因此setTimeout的第2個(gè)參數(shù)是最少延遲時(shí)間,而非等待時(shí)間。
當(dāng)我們預(yù)期到一個(gè)操作會(huì)很繁重耗時(shí)又不想阻塞主線程的執(zhí)行時(shí),會(huì)使用立即執(zhí)行任務(wù):
setTimeout(fn, 0);
然而考慮這么一段代碼會(huì)怎么執(zhí)行:
setTimeout(()=>{console.log(5)},5) setTimeout(()=>{console.log(4)},4) setTimeout(()=>{console.log(3)},3) setTimeout(()=>{console.log(2)},2) setTimeout(()=>{console.log(1)},1) setTimeout(()=>{console.log(0)},0)
了解完事件隊(duì)列機(jī)制,你的答案應(yīng)該是0,1,2,3,4,5
,然而答案卻是1,0,2,3,4,5
,這個(gè)是因?yàn)闉g覽器的實(shí)現(xiàn)機(jī)制是最小間隔為1ms。
// https://github.com/nodejs/node/blob/v8.9.4/lib/timers.js#L456 if (!(after >= 1 && after <= TIMEOUT_MAX)) after = 1; // schedule on next tick, follows browser behavior
瀏覽器以32位bit來存儲(chǔ)延時(shí),如果大于 2^32-1 ms(24.8天)
,導(dǎo)致溢出會(huì)立刻執(zhí)行。
定時(shí)器的嵌套調(diào)用超過4層時(shí),會(huì)導(dǎo)致最小間隔為4ms:
var i=0; function cb() { console.log(i, new Date().getMilliseconds()); if (i < 20) setTimeout(cb, 0); i++; } setTimeout(cb, 0);
可以看到前4層也不是標(biāo)準(zhǔn)的立刻執(zhí)行,在第4層后間隔明顯變大到4ms以上:
0 667 1 669 2 670 3 672 4 676 5 681 6 685
Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.
為了優(yōu)化后臺(tái)tab的加載占用資源,瀏覽器對(duì)后臺(tái)未激活的頁面中定時(shí)器延遲限制為1s。
對(duì)追蹤型腳本,如谷歌分析等,在當(dāng)前頁面,依然是4ms的延時(shí)限制,而后臺(tái)tabs為10s。
setInterval定時(shí)任務(wù)
此時(shí),我們會(huì)知道,setInterval會(huì)在每個(gè)定時(shí)器延時(shí)時(shí)間到了后,將一個(gè)新的事件fn放入事件隊(duì)列,如果前面的任務(wù)執(zhí)行太久,我們會(huì)看到連續(xù)的fn事件被執(zhí)行而感覺不到時(shí)間預(yù)設(shè)間隔。
因此,我們要盡量避免使用setInterval,改用setTimeout來模擬循環(huán)定時(shí)任務(wù)。
睡眠函數(shù)
JS一直缺少休眠的語法,借助ES6新的語法,我們可以模擬這個(gè)功能,但是同樣的這個(gè)方法因?yàn)榻柚藄etTimeout也不能保證準(zhǔn)確的睡眠延時(shí):
function sleep(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }) } // 使用 async function test() { await sleep(3000); }
async函數(shù)是Generator函數(shù)的語法糖,提供更方便的調(diào)用和語義,上面的使用可以替換為:
function* test() { yield sleep(3000); } // 使用 var g = test(); test.next();
但是調(diào)用使用更加復(fù)雜,因此一般我們使用async函數(shù)即可。但JS時(shí)如何實(shí)現(xiàn)睡眠函數(shù)的呢,其實(shí)就是提供一種執(zhí)行時(shí)的中間狀態(tài)暫停,然后將控制權(quán)移交出去,等控制權(quán)再次交回時(shí),從上次的斷點(diǎn)處繼續(xù)執(zhí)行。因此營(yíng)造了一種睡眠的假象,其實(shí)JS主線程還可以在執(zhí)行其他的任務(wù)。
Generator函數(shù)調(diào)用后會(huì)返回一個(gè)內(nèi)部指針,指向多個(gè)異步任務(wù)的暫停點(diǎn),當(dāng)調(diào)用next函數(shù)時(shí),從上一個(gè)暫停點(diǎn)開始執(zhí)行。
協(xié)程(coroutine)是指多個(gè)線程互相協(xié)作,完成異步任務(wù)的一種多任務(wù)異步執(zhí)行的解決方案。他的運(yùn)行流程:
● 協(xié)程A開始執(zhí)行
● 協(xié)程A執(zhí)行到一半,進(jìn)入暫停,執(zhí)行權(quán)轉(zhuǎn)移到協(xié)程B
● 協(xié)程B在執(zhí)行一段時(shí)間后,將執(zhí)行權(quán)交換給A
● 協(xié)程A恢復(fù)執(zhí)行
可以看到這也就是Generator函數(shù)的實(shí)現(xiàn)方案。
宏任務(wù)和微任務(wù)
一個(gè)JS的任務(wù)可以定義為:在標(biāo)準(zhǔn)執(zhí)行機(jī)制中,即將被調(diào)度執(zhí)行的所有代碼塊。
我們上面介紹了JS如何使用單線程完成異步多任務(wù)調(diào)用,但我們知道JS的異步任務(wù)分很多種,如setTimeout定時(shí)器、Promise異步回調(diào)任務(wù)等,它們的執(zhí)行優(yōu)先級(jí)又一樣嗎?
答案是不。JS在異步任務(wù)上有更細(xì)致的劃分,它分為兩種:
宏任務(wù)(macrotask)包含:
● 執(zhí)行的一段JS代碼塊,如控制臺(tái)、script元素中包含的內(nèi)容。
● 事件綁定的回調(diào)函數(shù),如點(diǎn)擊事件。
● 定時(shí)器創(chuàng)建的回調(diào),如setTimeout和setInterval。
微任務(wù)(microtask)包含:
● Promise對(duì)象的thenable函數(shù)。
● Nodejs中的process.nextTick函數(shù)。
● JS專用的queueMicrotask()函數(shù)。
宏任務(wù)和微任務(wù)都有自身的事件循環(huán)機(jī)制,也擁有獨(dú)立的事件隊(duì)列(Event Queue),都會(huì)按照隊(duì)列的順序依次執(zhí)行。但宏任務(wù)和微任務(wù)主要有兩點(diǎn)區(qū)別:
1、宏任務(wù)執(zhí)行完成,在控制權(quán)交還給主線程執(zhí)行其他宏任務(wù)之前,會(huì)將微任務(wù)隊(duì)列中的所有任務(wù)執(zhí)行完成。
2、微任務(wù)創(chuàng)建的新的微任務(wù),會(huì)在下一個(gè)宏任務(wù)執(zhí)行之前被繼續(xù)遍歷執(zhí)行,直到微任務(wù)隊(duì)列為空。
瀏覽器的進(jìn)程和線程
瀏覽器是多進(jìn)程式的,每個(gè)頁面和插件都是一個(gè)獨(dú)立的進(jìn)程,這樣可以保證單頁面崩潰或者插件崩潰不會(huì)影響到其他頁面和瀏覽器整體的穩(wěn)定運(yùn)行。
它主要包括:
1、主進(jìn)程:負(fù)責(zé)瀏覽器界面顯示和管理,如前進(jìn)、后退,新增、關(guān)閉,網(wǎng)絡(luò)資源的下載和管理。
2、第三方插件進(jìn)程:當(dāng)啟用插件時(shí),每個(gè)插件獨(dú)立一個(gè)進(jìn)程。
3、GPU進(jìn)程:全局唯一,用于3D圖形繪制。
4、Renderer渲染進(jìn)程:每個(gè)頁面一個(gè)進(jìn)程,互不影響,執(zhí)行事件處理、腳本執(zhí)行、頁面渲染。
瀏覽器的單個(gè)頁面就是一個(gè)進(jìn)程,指的就是Renderer進(jìn)程,而進(jìn)程中又包含有多個(gè)線程用于處理不同的任務(wù),主要包括:
1、GUI渲染線程:負(fù)責(zé)HTML和CSS的構(gòu)建成DOM樹,渲染頁面,比如重繪。
2、JS引擎線程:JS內(nèi)核,如Chrome的V8引擎,負(fù)責(zé)解析執(zhí)行JS代碼。
3、事件觸發(fā)線程:如點(diǎn)擊等事件存在綁定回調(diào)時(shí),觸發(fā)后會(huì)被放入宏任務(wù)事件隊(duì)列。
4、定時(shí)觸發(fā)器線程:setTimeout和setInterval的定時(shí)計(jì)數(shù)器,在時(shí)間到達(dá)后放入宏任務(wù)事件隊(duì)列。
5、異步HTTP請(qǐng)求線程:XMLHTTPRequest請(qǐng)求后新開一個(gè)線程,等待狀態(tài)改變后,如果存在回調(diào)函數(shù),就將其放入宏任務(wù)隊(duì)列。
需要注意的是,GUI渲染進(jìn)程和JS引擎進(jìn)程互斥,兩者只會(huì)同時(shí)執(zhí)行一個(gè)。主要的原因是為了節(jié)流,因?yàn)镴S的執(zhí)行會(huì)可能多次改變頁面,頁面的改變也會(huì)多次調(diào)用JS,如resize。因此瀏覽器采用的策略是交替執(zhí)行,每個(gè)宏任務(wù)執(zhí)行完成后,執(zhí)行GUI渲染,然后執(zhí)行下一個(gè)宏任務(wù)。
因?yàn)镴S只有一個(gè)引擎線程,同時(shí)和GUI渲染線程互斥,因此在繁重任務(wù)執(zhí)行時(shí)會(huì)導(dǎo)致頁面卡住,所以在HTML5中支持了Webworker,它用于向?yàn)g覽器申請(qǐng)一個(gè)新的子線程執(zhí)行任務(wù),并通過postMessage API來和worker線程通信。所以我們?cè)诜敝厝蝿?wù)執(zhí)行時(shí),可以選擇新開一個(gè)Worker線程來執(zhí)行,并在執(zhí)行結(jié)束后通信給主線程,這樣不會(huì)影響頁面的正常渲染和使用。
總結(jié)
1、JS是單線程、阻塞式執(zhí)行語言。
2、JS通過事件循環(huán)機(jī)制來完成異步任務(wù)并發(fā)執(zhí)行。
3、JS將任務(wù)細(xì)分為宏任務(wù)和微任務(wù)來提供執(zhí)行優(yōu)先級(jí)。
4、瀏覽器單頁面為一個(gè)進(jìn)程,包含的JS引擎線程和GUI渲染線程互斥,可以通過新開Web Worker線程來完成繁重的計(jì)算任務(wù)。
最后給大家出一個(gè)考題,可以猜下執(zhí)行的輸出結(jié)果來驗(yàn)證學(xué)習(xí)成果:
function sleep(ms) { console.log('before first microtask init'); new Promise(resolve => { console.log('first microtask'); resolve() }) .then(() => {console.log('finish first microtask')}); console.log('after first microtask init'); return new Promise(resolve => { console.log('second microtask'); setTimeout(resolve, ms); }); } setTimeout(async () => { console.log('start task'); await sleep(3000); console.log('end task'); }, 0); setTimeout(() => console.log('add event'), 0); console.log('main thread');
輸出為:
main thread start task before first microtask init first microtask after first microtask init second microtask finish first microtask add event end task
關(guān)于JavaScript的單線程和事件循環(huán)是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
網(wǎng)頁標(biāo)題:JavaScript的單線程和事件循環(huán)是什么
文章鏈接:http://chinadenli.net/article8/gichop.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、虛擬主機(jī)、網(wǎng)站排名、做網(wǎng)站、商城網(wǎng)站、標(biāo)簽優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)