中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript的單線程和事件循環是什么

發布時間:2020-06-23 15:16:32 來源:億速云 閱讀:185 作者:元一 欄目:web開發

JavaScript的單線程和事件循環是什么?針對這個問題,今天小編總結了這篇文章,希望能幫助更多想解決這個問題的朋友找到更加簡單易行的辦法。

單線程就是進程只有一個線程,線程較多線程來說,系統穩定、擴展性極強、軟件豐富。多用于點對點的服務。

"單線程"語言

在瀏覽器實現中,每個單頁都是一個獨立進程,其中包含了JS引擎、GUI界面渲染、事件觸發、定時觸發器、異步HTTP請求等多個線程。

進程(Process)是操作系統CPU等資源分配的最小單位,是程序的執行實體,是線程的容器。
線程(Thread)是操作系統能夠進行運算調度的最小單位,一條線程指的是進程中一個單一順序的控制流。

因此我們可以說JS是"單線程"式的語言,代碼只能按照單一順序進行串行執行,并在執行完成前阻塞其他代碼。

JS數據結構

JavaScript的單線程和事件循環是什么

如上圖所示為JS的幾種重要數據結構:

 ● 棧(Stack):用于JS的函數嵌套調用,后進先出,直到棧被清空。

 ● 堆(Heap):用于存儲大塊數據的內存區域,如對象。

 ● 隊列(Queue):用于事件循環機制,先進先出,直到隊列為空。

事件循環

javascript是單線程的語言,也就是說,同一個時間只能做一件事。而這個單線程的特性,與它的用途有關,作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為準?

為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質

我們的經驗告訴我們JS是可以并發執行的,比如定時任務、并發AJAX請求,那這些是怎么完成的呢?其實這些都是JS在用單線程模擬多線程完成的。

JavaScript的單線程和事件循環是什么

如上圖所示,JS串行執行主線程任務,當遇到異步任務如定時器時,將其放入事件隊列中,在主線程任務執行完畢后,再去事件隊列中遍歷取出隊首任務進行執行,直至隊列為空。

全部執行完成后,會有主監控進程,持續檢測隊列是否為空,如果不為空,則繼續事件循環。

setTimeout定時任務

定時任務setTimeout(fn, timeout)會先被交給瀏覽器的定時器模塊,等延遲時間到了,再將事件放入到事件隊列里,等主線程執行結束后,如果隊列中沒有其他任務,則會被立即處理,而如果還有沒有執行完成的任務,則需要等前面的任務都執行完成才會被執行。因此setTimeout的第2個參數是最少延遲時間,而非等待時間。

當我們預期到一個操作會很繁重耗時又不想阻塞主線程的執行時,會使用立即執行任務:

setTimeout(fn, 0);

特殊場景1:最小延遲為1ms

然而考慮這么一段代碼會怎么執行:

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)

了解完事件隊列機制,你的答案應該是0,1,2,3,4,5,然而答案卻是1,0,2,3,4,5,這個是因為瀏覽器的實現機制是最小間隔為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來存儲延時,如果大于 2^32-1 ms(24.8天),導致溢出會立刻執行。

特殊場景2:最小延遲為4ms

定時器的嵌套調用超過4層時,會導致最小間隔為4ms:

var i=0;
function cb() {
    console.log(i, new Date().getMilliseconds());
    if (i < 20) setTimeout(cb, 0);
    i++;
}
setTimeout(cb, 0);

可以看到前4層也不是標準的立刻執行,在第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.

特殊場景3:瀏覽器節流

為了優化后臺tab的加載占用資源,瀏覽器對后臺未激活的頁面中定時器延遲限制為1s。
對追蹤型腳本,如谷歌分析等,在當前頁面,依然是4ms的延時限制,而后臺tabs為10s。

setInterval定時任務

此時,我們會知道,setInterval會在每個定時器延時時間到了后,將一個新的事件fn放入事件隊列,如果前面的任務執行太久,我們會看到連續的fn事件被執行而感覺不到時間預設間隔。

因此,我們要盡量避免使用setInterval,改用setTimeout來模擬循環定時任務。

睡眠函數

JS一直缺少休眠的語法,借助ES6新的語法,我們可以模擬這個功能,但是同樣的這個方法因為借助了setTimeout也不能保證準確的睡眠延時:

function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  })
}
// 使用
async function test() {
    await sleep(3000);
}

async await機制

async函數是Generator函數的語法糖,提供更方便的調用和語義,上面的使用可以替換為:

function* test() {
    yield sleep(3000);
}
// 使用
var g = test();
test.next();

但是調用使用更加復雜,因此一般我們使用async函數即可。但JS時如何實現睡眠函數的呢,其實就是提供一種執行時的中間狀態暫停,然后將控制權移交出去,等控制權再次交回時,從上次的斷點處繼續執行。因此營造了一種睡眠的假象,其實JS主線程還可以在執行其他的任務。

Generator函數調用后會返回一個內部指針,指向多個異步任務的暫停點,當調用next函數時,從上一個暫停點開始執行。

協程

協程(coroutine)是指多個線程互相協作,完成異步任務的一種多任務異步執行的解決方案。他的運行流程:

 ● 協程A開始執行

 ● 協程A執行到一半,進入暫停,執行權轉移到協程B

 ● 協程B在執行一段時間后,將執行權交換給A

 ● 協程A恢復執行

可以看到這也就是Generator函數的實現方案。

宏任務和微任務

一個JS的任務可以定義為:在標準執行機制中,即將被調度執行的所有代碼塊。

我們上面介紹了JS如何使用單線程完成異步多任務調用,但我們知道JS的異步任務分很多種,如setTimeout定時器、Promise異步回調任務等,它們的執行優先級又一樣嗎?

答案是不。JS在異步任務上有更細致的劃分,它分為兩種:

宏任務(macrotask)包含:

 ● 執行的一段JS代碼塊,如控制臺、script元素中包含的內容。

 ● 事件綁定的回調函數,如點擊事件。

 ● 定時器創建的回調,如setTimeout和setInterval。

微任務(microtask)包含:

 ● Promise對象的thenable函數。

 ● Nodejs中的process.nextTick函數。

 ● JS專用的queueMicrotask()函數。

JavaScript的單線程和事件循環是什么

宏任務和微任務都有自身的事件循環機制,也擁有獨立的事件隊列(Event Queue),都會按照隊列的順序依次執行。但宏任務和微任務主要有兩點區別:

1、宏任務執行完成,在控制權交還給主線程執行其他宏任務之前,會將微任務隊列中的所有任務執行完成。

2、微任務創建的新的微任務,會在下一個宏任務執行之前被繼續遍歷執行,直到微任務隊列為空。

瀏覽器的進程和線程

瀏覽器是多進程式的,每個頁面和插件都是一個獨立的進程,這樣可以保證單頁面崩潰或者插件崩潰不會影響到其他頁面和瀏覽器整體的穩定運行。

它主要包括:

1、主進程:負責瀏覽器界面顯示和管理,如前進、后退,新增、關閉,網絡資源的下載和管理。

2、第三方插件進程:當啟用插件時,每個插件獨立一個進程。

3、GPU進程:全局唯一,用于3D圖形繪制。

4、Renderer渲染進程:每個頁面一個進程,互不影響,執行事件處理、腳本執行、頁面渲染。

單頁面線程

瀏覽器的單個頁面就是一個進程,指的就是Renderer進程,而進程中又包含有多個線程用于處理不同的任務,主要包括:

1、GUI渲染線程:負責HTML和CSS的構建成DOM樹,渲染頁面,比如重繪。

2、JS引擎線程:JS內核,如Chrome的V8引擎,負責解析執行JS代碼。

3、事件觸發線程:如點擊等事件存在綁定回調時,觸發后會被放入宏任務事件隊列。

4、定時觸發器線程:setTimeout和setInterval的定時計數器,在時間到達后放入宏任務事件隊列。

5、異步HTTP請求線程:XMLHTTPRequest請求后新開一個線程,等待狀態改變后,如果存在回調函數,就將其放入宏任務隊列。

需要注意的是,GUI渲染進程和JS引擎進程互斥,兩者只會同時執行一個。主要的原因是為了節流,因為JS的執行會可能多次改變頁面,頁面的改變也會多次調用JS,如resize。因此瀏覽器采用的策略是交替執行,每個宏任務執行完成后,執行GUI渲染,然后執行下一個宏任務。

Webworker線程

因為JS只有一個引擎線程,同時和GUI渲染線程互斥,因此在繁重任務執行時會導致頁面卡住,所以在HTML5中支持了Webworker,它用于向瀏覽器申請一個新的子線程執行任務,并通過postMessage API來和worker線程通信。所以我們在繁重任務執行時,可以選擇新開一個Worker線程來執行,并在執行結束后通信給主線程,這樣不會影響頁面的正常渲染和使用。

總結

1、JS是單線程、阻塞式執行語言。

2、JS通過事件循環機制來完成異步任務并發執行。

3、JS將任務細分為宏任務和微任務來提供執行優先級。

4、瀏覽器單頁面為一個進程,包含的JS引擎線程和GUI渲染線程互斥,可以通過新開Web Worker線程來完成繁重的計算任務。

JavaScript的單線程和事件循環是什么

最后給大家出一個考題,可以猜下執行的輸出結果來驗證學習成果:

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

關于JavaScript的單線程和事件循環是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

郓城县| 彩票| 华坪县| 朝阳区| 灵石县| 峨山| 普安县| 洪湖市| 南安市| 澎湖县| 翁源县| 长子县| 蕲春县| 哈巴河县| 临安市| 昂仁县| 锦州市| 垦利县| 华亭县| 阿瓦提县| 黑龙江省| 赤水市| 晋城| 通河县| 鄂伦春自治旗| 淮安市| 商水县| 龙川县| 博罗县| 红安县| 浦江县| 太原市| 长岭县| 铁力市| 尼勒克县| 高淳县| 迁西县| 布尔津县| 淮滨县| 新巴尔虎左旗| 彩票|