本章瞭解一下js的運行原理,瞭解了js的運行原理才能寫出更優美的代碼,提升運行效率,還能解決開發中遇到的不理解的問題。
進程是cpu資源分配的最小單位,進程能夠包含多個線程。 瀏覽器就是多進程的,每打開的一個瀏覽器窗口就是一個進程。css
線程是cpu調度的最小單位,同一進程下的各個線程之間共享程序的內存空間。html
能夠把進程看作一個倉庫,線程是能夠運輸的貨車,每一個倉庫有屬於本身的多輛貨車爲倉庫服務(運貨),每一個倉庫能夠同時由多輛車同時拉貨,可是每輛車同一時間只能幹一件事,就是運輸本次的貨物。這樣就好理解了吧。面試
瀏覽器包括4個進程:瀏覽器
渲染進程主要包括GUI渲染線程、Js引擎線程、事件循環線程、定時器線程、http異步線程。性能優化
先看看瀏覽器獲得一個網站資源後幹了哪些事:網絡
GUI就是來幹這個事情的,若是修改了一些元素的顏色或者背景色,頁面就會重繪(Repaint),若是修改元素的尺寸,頁面就會迴流(Reflow),當頁面須要Repaing和Reflow時GUI多會執行,進行頁面繪製。異步
這裏提示一點:Reflow比Repaint的成本更高,在js性能優化中會將如何避免Reflow和Repaint函數
js引擎線程就是js內核,負責解析與執行js代碼,也稱爲主線程。瀏覽器同時只能有一個JS引擎線程在運行JS程序,因此js是單線程運行的。oop
須要注意的是,js引擎線程和GUI渲染線程同時只能有一個工做,js引擎線程會阻塞GUI渲染線程post
<html> <body> <div id="div1"> a </div> <script> document.getElementById('div1').innerHTML = 'b' </script> <div id='div2'> div2 </div> </body> </html>
在瀏覽器渲染的時候遇到<script>標籤,就會中止GUI的渲染,而後js引擎線程開始工做,執行裏面的js代碼,等js執行完畢,js引擎線程中止工做,GUI繼續渲染下面的內容。因此若是js執行時間太長就會形成頁面卡頓的狀況,這也是後面性能優化的點。
事件循環線程用來管理控制事件循環,而且管理着一個事件隊列(task queue),當js執行碰到事件綁定和一些異步操做時,會把對應的事件添加到對應的線程中(好比定時器操做,便把定時器事件添加到定時器線程),等異步事件有告終果,便把他們的回調操做添加到事件隊列,等待js引擎線程空閒時來處理。
因爲js是單線程運行,因此不能抽出時間來計時,只能另開闢一個線程來處理定時器任務,等計時完成,把定時器要執行的操做添加到事件任務隊列尾,等待js引擎線程來處理。這個線程就是定時器線程。
當執行到一個http異步請求時,便把異步請求事件添加到異步請求線程,等收到響應(準確來講應該是http狀態變化),把回調函數添加到事件隊列,等待js引擎線程來執行。
上面介紹了渲染進程中的5個主要的線程,可能看完上面對各個線程簡單的介紹,還有點不明白他們之間到底怎麼協做工做的,下面就從Event Loop的角度來聊一聊他們之間是怎樣那麼愉快合做的。
已經知道了js是單線程運行的,也知道js中有同步操做和異步操做。同步和異步你們應該很熟了,很少介紹。
同步操做運行在js引擎線程(主線程)上,會造成一個執行棧,而異步操做則在他們對應的異步線程上處理(好比:定時操做在定時器線程上;http請求則在異步請求線程上處理)。
而事件循環線程則監視着這些異步線程們,等異步線程們裏面的操做有告終果(好比:定時器計時完成,或者http請求獲取到響應),便把他們的毀掉函數添加到事件隊列尾部,整個過程當中執行棧、事件隊列就構成Event Loop。
請看網絡盜圖:
這是網絡上對Event Loop的解釋圖,相信你們如今能明白這張圖的含義了。
定時器是規定在一段時間以後執行一段代碼,可是在js執行中不會準確無誤的按照預期的時間去執行定時器裏面的代碼。
一個緣由是W3C標準規定setTimeout中最小的時間週期是4毫秒,凡是低於4ms的時間間隔都按照4ms來處理。
其實還有一個重要的緣由,若是仔細看上面的文章,你們應該會想到在js執行的時候,主線程碰到定時器的時候,是不會直接處理的,應該是先把定時器事件交給定時器線程去處理,這時主線程繼續執行下面的代碼,同時定時器線程開始計時處理,等到計時完畢,事件循環線程會把定時器要執行的操做放在事件隊列末尾,等主線程空閒的時候再來執行事件隊列裏面的操做。
使用setTimeout模擬setInterval代碼相似如下代碼:
var say = function() { setTimeout(say, 1000) console.log('hello world') } setTimeout(say, 1000)
這樣js碰到定時器,會交給定時器線程處理,而後等計時完畢,定時器裏面的操做添加到事件隊列,等主線程空閒去執行,主線程執行的時候又會發遇到定時器,這是又開始執行上面的一系列操做。
你會發現,這樣作會在每一次定時器執行完畢纔開始下一個定時器,其中的偏差只是等待主線程空閒所須要等待的時間。
而setInterval是規定每隔固定的時間就往定時器線程中推入一個事件,這樣作有一個問題,就是累積效應。
累積效應會致使有些事件丟失,具體爲何會丟失,感興趣的能夠看這篇文章,因此爲了保險起見,儘可能去使用setTimeout而不使用setInterval。
若是有對setTimeout很是感興趣的同窗,我很是推薦你們去看看80% 應聘者都不及格的 JS 面試題這篇文章。
microtask是Promise裏一個新的概念。
請看網絡盜圖:
因此js運行過程:
有關macrotask和microtask的分析借鑑於從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理