你們都知道JavaScript是單線程的,單線程就意味着同一時間只能作一件事,那麼有同窗會問,爲何JavaScript的做者不把它設計成多線程的呢,那樣性能不是更好。爲了回答這個問題,咱們得從JavaScript的用途上來解釋了,因爲JavaScript是一門腳本語言,被用於與用戶進行交互和操做DOM有關,若是是多線程的話, 會出現不少複雜的同步問題,讓JavaScript的操做變得難以控制。假如如今有一個線程A在dom上新增一個節點a,另外一個線程又在dom上刪除了節點a,那麼咱們該以哪一個線程爲標準呢。因此,對於JavaScript單線程這一特色,將來也不會改變。對於一些JavaScript開發者來講,JavaScript的運行機制一直困擾着一些同窗,好比異步請求的執行問題,爲何js代碼會形成頁面渲染的阻塞,做用域中的變量提高等等到底作了什麼,看完下面的文章你應該會對這些問題有清楚的瞭解。javascript
咱們常常說,JavaScript是單線程的,那到底什麼是線程呢。官方的說法是,進程是CPU資源分配的最小單位,而線程是CPU調度的最小單位。你們看到這句話可能有些懵。那以瀏覽器爲例,當咱們在瀏覽器中打開一個新的標籤頁Tab的時候,CPU會爲瀏覽器分配一個新的進程,去渲染咱們的網頁,而渲染網頁的工做是經過這個進程中的多個線程來配合完成的,包括瀏覽器的渲染線程、JS引擎線程、http異步請求線程等等。因此,一個進程由多個線程組成,每一個線程是進程的不一樣執行路線。而進程與進程之間是相對獨立的,如:在瀏覽器打開兩個標籤頁Tab,就是兩個進程,這兩個標籤頁的運行是互不影響的。
html
說到瀏覽器內核,就不得不提到五大主流瀏覽器IE(IE內核),Chrome瀏覽器(之前是Webkit內核,如今是Blink內核),Safari(Webkit內核),Firefox(Gecko內核),Opera(最開始是Pestro內核,而後是Webkit內核,最後是Blink內核),也正是由於不一樣瀏覽器的內核不一樣,致使有些相同html元素在不一樣瀏覽器上的表現不一樣,這主要是因爲瀏覽器內核中的GUI渲染線程不一樣所致使。
java
瀏覽器內核是多線程的,在內核的控制下,多個線程相互配合以保持同步,一個瀏覽器內核一般由如下幾個線程組成:ios
一、GUI渲染線程
二、JS引擎線程
三、定時器觸發線程
四、事件觸發線程
五、異步HTTP請求線程ajax
單線程就意味着,全部任務的執行都須要排隊,前一個任務結束,後一個任務才能執行,若是一個任務耗時很長,後一個任務不得不一直等待着。JavaScript的做者意識到這個問題,將全部任務劃分爲兩種,一種是同步任務,一種是異步任務。同步任務是指在主線程上排隊執行的任務,前一個任務結束,後一個任務才能執行。異步任務是指不進入主線程執行,而進入「任務隊列」的任務,只有當「任務隊列」通知主線程能夠執行了,該任務纔會進入主線程。異步任務分爲兩種,宏任務和微任務(後面會重點介紹)。接下來經過兩個例子來講明同步任務和異步任務的主要區別:axios
console.log('a')
while (true) {
console.log('這裏是while')
}
console.log('b')複製代碼
最後打印的結果是a,由於上述代碼均屬於同步任務,由上到下依次執行,當主線程執行完console.log('a')以後,開始執行while循環出現死循環,無限執行console.log('這裏是while'),致使內存溢出,致使while循環後面的任務就沒法執行了。瀏覽器
console.log('a')
settimeout(function () {
console.log('settimeout1')
},0)
while (true) {
console.log('這裏是while')
} 複製代碼
最後的打印結果仍是a,由於這段代碼中同時存在同步任務和異步任務,異步任務要等到主線程上全部的同步任務執行完成以後才能執行。上述代碼中的console.log('a')和while循環均屬於同步任務,而settimeout屬於異步任務(在後面的事件循環中會介紹哪些事件屬於異步任務),因此當執行完console.log('a')以後,主線程將執行while循環,無限執行console.log('這裏是while'),最後致使內存溢出,沒法執行下面的代碼了
多線程
下圖爲一個完整的事件循環的過程:dom
事件循環的運行機制:異步
全局上下文(script 標籤)被推入執行棧,同步代碼執行。在執行的過程當中,會判斷是同步任務仍是異步任務,經過對一些接口的調用,能夠產生新的宏任務與微任務,它們會分別被推入各自的任務隊列裏。同步代碼執行完了,全局script腳本會被移出宏隊列,這個過程本質上是隊列的宏任務的執行和出隊的過程。
上一步咱們出隊的是一個宏任務,這一步咱們處理的是微任務。但須要注意的是:當宏任務出隊時,任務是一個一個執行的;而微任務出隊時,任務是一隊一隊執行的。所以,咱們處理微任務隊列這一步,會逐個執行隊列中的任務並把它出隊,直到隊列被清空。
主線程從「任務隊列」讀取事件這個過程,是循環不斷的,因此整個這種運行機制就叫作Event Loop(事件循環)。每當主線程爲空時,就會去讀取「事件隊列」,這就是JavaScript的運行機制
咱們在上面提到,異步任務分爲宏任務和微任務:
當主線程上的全部同步任務執行完以後,是先執行宏任務仍是先執行微任務呢?
如下經過一個例子來理解異步任務的運行機制:
Promise.resolve().then(() => {
console.log('Promse1')
setTimeout(function () {
console.log('setTimeout1')
}, 0)
})
setTimeout(function () {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('Promise2')
})
}, 0)複製代碼
最終打印結果依次爲Promise一、setTimeout二、Promise二、setTimeout1