欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

JavaScript的單線程和事件循環(huán)是什么

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)

JavaScript的單線程和事件循環(huán)是什么

如上圖所示為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在用單線程模擬多線程完成的。

JavaScript的單線程和事件循環(huán)是什么

如上圖所示,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);

特殊場(chǎng)景1:最小延遲為1ms

然而考慮這么一段代碼會(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í)行。

特殊場(chǎng)景2:最小延遲為4ms

定時(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.

特殊場(chǎng)景3:瀏覽器節(jié)流

為了優(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 await機(jī)制

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é)程

協(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ù)。

JavaScript的單線程和事件循環(huán)是什么

宏任務(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ù)。

Webworker線程

因?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ù)。

JavaScript的單線程和事件循環(huán)是什么

最后給大家出一個(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)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司