天天看點

javascript事件循環Event Loop

js Event Loop學習筆記。

1、線程和程序的差別

一個程式至少有一個程序,一個程序至少有一個線程。線程的劃分尺度小于程序,使得多線程程式的并發性高。

另外,程序在執行過程中擁有獨立的記憶體單元,而多個線程共享記憶體,進而極大的提高了程式的運作效率。

線程在執行過程中與程序還是有差別的,每個獨立的線程有一個程式運作的入口、順序執行序列和程式的出口。但是線程不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制。

從邏輯角度來看,多線程意義在于一個應用程式中,有多個執行部分可以同時執行。但是作業系統并沒有将多個線程看做多個獨立的應用,來實作程序的排程和管理及資源配置設定。

2、js為什麼是單線程的

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

3、單線程的優劣勢

優勢:**

1、降低處理複雜性,簡化開發。

2、作為用于預處理與使用者互動的腳本語言,可以更加容易的處理狀态同步的問題。

劣勢:

無并發處理能力,任務處于I/O等待狀态,導緻CPU處理資源的浪費。

于是JavaScript語言将任務分為:同步任務和異步任務。

4、同步

如果在函數傳回的時候,調用者就能夠得到預期的結果(即拿到預期的傳回值或者看到了預期的結果),那麼這個函數就是同步的。

Math.sqrt(4);
console.log('Hi');
           

第一個函數傳回時,就拿到了預期的傳回值:4的平方根。第二個函數傳回時,就看到了預期的效果:在控制台列印字元串Hi。是以這兩個函數是同步的。

5、異步

如果在函數傳回的時候,調用者還不能夠拿到預期的結果,而是需要在将來通過一定的手段得到,那麼這個函數就是異步的。

fs.readFile('test.txt', 'utf8', function(err, data) {
    console.log(data);
});
           

在上面的代碼中,我們希望通過fs.readFile函數讀取檔案test.txt中的内容,并列印出來。但是在fs.readFile函數傳回時,我們期望的結果并不會發生,而是要等到檔案全部讀取完成之後。如果檔案很大的話可能要很長時間,是以,fs.readFile函數是異步的。

正是由于JavaScript是單線程的,而異步容易實作非阻塞,是以在JavaScript中對于耗時的操作或者時間不确定的操作,使用異步就成了必然的選擇。

從上文可以看出,異步函數實際上很快就調用完成了。但是後面還有執行異步操作、通知主線程、主線程調用回調函數等很多步驟。我們把整個過程叫做異步過程。異步函數的調用在整個異步過程中,隻是一小部分

一個異步過程通常是這樣的:主線程發起一個異步請求,異步任務接收請求并告知主線程已收到(異步函數傳回);主線程可以繼續執行後面的代碼,同時異步操作開始執行;執行完成後通知主線程;主線程收到通知後,執行一定的動作(調用回調函數)

是以,一個異步過程包括兩個要素:注冊函數和回調函數,其中注冊函數用來發起異步過程,回調函數用來處理結果。

6、任務隊列

對于同步任務來說,按順序執行即可;但是,對于異步任務,各任務執行的時間長短不同,執行完成的時間點也不同,主線程如何調控異步任務呢?這就用到了消息隊列

有些文章把消息隊列稱為任務隊列,或者叫事件隊列,總之是和異步任務相關的隊列

可以确定的是,它是隊列這種先入先出的資料結構,和排隊是類似的,哪個異步操作完成的早,就排在前面。不論異步操作何時開始執行,隻要異步操作執行完成,就可以到消息隊列中排隊

這樣**,主線程在空閑的時候,就可以從消息隊列中擷取消息并執行**

消息隊列中放的消息具體是什麼東西?消息的具體結構當然跟具體的實作有關。但是為了簡單起見,可以認為:消息就是注冊異步任務時添加的回調函數。

JS是有兩個任務隊列的,一個叫做Macrotask Queue(Task Queue),一個叫做Microtask Queue;

**Macrotask Queue(宏任務):**進行比較大型的工作,常見的有setTimeout,setInterval,使用者互動操作,UI渲染等;

**Microtask Queue(微任務):**進行較小的工作,常見的有Promise、new MutaionObserver()、Process.nextTick;

任務隊列中的微任務先執行,然後執行宏任務。

7、事件循環 Event Loop

JavaScript調控同步和異步的機制叫做事件循環。

javascript事件循環Event Loop

事件循環大緻分為以下幾個步驟:

(1)所有同步任務都在主線程上執行,形成一個執行棧。

(2)主線程之外,還存在一個"任務隊列"。隻要異步任務有了運作結果,就在"任務隊列"之中放置一個事件。

(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看裡面有哪些事件。那些對應的異步任務,于是結束等待狀态,進入執行棧,開始執行。

(4)主線程不斷重複上面的第三步。

例子,看下面程式的輸出結果:

console.log('start');//同步任務
 const interval = setInterval(() => {//異步任務,進入任務隊列,宏任務
       console.log('setInterval');
   }, 0);
   setTimeout(() => {/異步任務,進入任務隊列,宏任務
       console.log('setTimeout 1');
       Promise.resolve()
           .then(() => {
               console.log('promise1');
           }).then(() => {
               setTimeout(() => {
                   console.log('setTimeout 2');
                   clearInterval(interval);
               }, 0);
           })
   }, 0);
   Promise.resolve()
       .then(() => {/異步任務,進入任務隊列,微任務
           console.log('promise2');
       });
           

結果:

javascript事件循環Event Loop

繼續閱讀