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調控同步和異步的機制叫做事件循環。
事件循環大緻分為以下幾個步驟:
(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');
});
結果: