1.在討論瀏覽器與JavaScript之前,我們先來簡單瞭解一下程式與執行緒
程式(process):資源分配的最小單位
程式是應用程式的執行例項,是作業系統進行資源分配和排程的一個獨立單位。
執行緒(thread):CPU排程的最小單位
執行緒是程式內部的一個執行單元,是被系統獨立排程和分派的基本單位。系統建立好程式後,實際上就啟動執行了該程式的主執行執行緒。
上面這樣講可能不是很容易理解,我們以工廠?模式來比喻:
- 計算機的核心是CPU,它承擔了所有的計算任務。它就像一座工廠?,時刻在執行著。
- 單個CPU一次只能執行一個任務
- 程式就好比工廠的車間?,它代表CPU所能處理的單個任務
- 一個車間裡,可以有很多工人?。他們協同完成一個任務
- 執行緒就好比車間裡的工人?,一個程式可以包含多個執行緒
2.瀏覽器多執行緒與Javascript單執行緒(單執行緒與多執行緒)
首先瀏覽器是多執行緒的,所以瀏覽器一次能夠處理多個事件,比如渲染頁面,指令碼執行,事件處理等。
JavaScript
是單執行緒的(瀏覽器只給JS分配了一個執行緒)
JavaScript單執行緒
單執行緒的特點就是一次只能處理一件事情。後一個任務需要等待前一個任務執行完再執行。
JS在單執行緒中實現非同步機制只要依靠瀏覽器的任務佇列
,任務佇列分為同步任務佇列
與非同步任務佇列
,非同步任務又可以分為巨集任務
與微任務
在同步任務自上而下執行時,如果遇到一個非同步任務,不會立即執行,而是把它放到非同步任務佇列中去排隊,當同步任務執行完成後,才會到非同步任務佇列進行查詢等待任務佇列中的內容(同步任務佇列完不成,不管非同步任務是否達到時間,都不執行),等達到條件後執行,然後再接著去非同步任務佇列中 查詢。這就是因為js是單執行緒的,一次只能處理一件事情
JavaScript為什麼要設計成單執行緒
JavaScript
之所以採用單執行緒,而不是多執行緒,跟它的歷史有關。最開始的JavaScript功能並沒有現在這麼強大,作為瀏覽器指令碼語言,它最初主要是用來處理頁面的使用者互動
,以及DOM的操作
。如果JavaScript
是多執行緒的話,假設存在兩個執行緒同時操作一個DOM,這時候瀏覽器就不知道應該處理哪個執行緒的操作結果了。
3.同步與非同步任務
**同步: ** 在一個執行緒上同一時間只能做一件事情,當情事情做完才能進行下一個任務。
非同步: 在主棧中執行一個任務,但是發現這個任務是一個非同步的操作,會將它放到非同步任務佇列中。
非同步任務又分為巨集任務與微任務:
巨集任務:定時器setTimeout
,setInterval
,事件繫結
,回撥函式
,node中的fs模組
微任務:new Promise().then(回撥)
,process.nextTick()
,async await
4.執行棧與任務佇列
1)執行棧:從名字可以看出,執行棧使用到的是資料結構中的棧結構, 它是一個儲存函式呼叫的棧結構,遵循先進後出的原則。它主要負責跟蹤所有要執行的程式碼。 每當一個函式執行完成時,就會從堆疊中彈出(pop)該執行完成函式;如果有程式碼需要進去執行的話,就進行 push 操作。
2)任務佇列: 從名字中可以看出,任務佇列使用到的是資料結構中的佇列結構,它用來儲存非同步任務,遵循先進先出的原則。它主要負責將新的任務傳送到佇列中進行處理。
執行順序:
JavaScript在執行程式碼時,會將同步的程式碼按照順序排在執行棧中,然後依次執行裡面的函式。當遇到非同步任務時,就將其放入任務佇列中,等待當前執行棧所有同步程式碼執行完成之後,就會從非同步任務佇列中取出已完成的非同步任務的回撥並將其放入執行棧中繼續執行,如此迴圈往復,直到執行完所有任務
先執行同步任務,執行完接著執行微任務,最後執行巨集任務。這個過程會不斷重複
下面看幾道經典面試題
console.log("script start");
async function async1() {
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2 end");
}
async1();
setTimeout(function () {
console.log("setTimeout");
}, 0);
new Promise((resolve) => {
console.log("Promise");
resolve();
})
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("script end");
// script start => async2 end => Promise => script end => async1 end=> promise1 => promise2 => setTimeout
解析:
1.首先執行同步任務:列印script start,async2 end,Promise,script end
2.同步任務執行完,開始執行微任務:async1 end,promise1,promise2
3.最後執行巨集任務:setTimeout