Js基礎知識(四) - js運行原理與機制

js運行機制

本章瞭解一下js的運行原理,瞭解了js的運行原理才能寫出更優美的代碼,提升運行效率,還能解決開發中遇到的不理解的問題。

進程與線程

進程是cpu資源分配的最小單位,進程能夠包含多個線程。 瀏覽器就是多進程的,每打開的一個瀏覽器窗口就是一個進程。css

線程是cpu調度的最小單位,同一進程下的各個線程之間共享程序的內存空間。html

能夠把進程看作一個倉庫,線程是能夠運輸的貨車,每一個倉庫有屬於本身的多輛貨車爲倉庫服務(運貨),每一個倉庫能夠同時由多輛車同時拉貨,可是每輛車同一時間只能幹一件事,就是運輸本次的貨物。這樣就好理解了吧。面試

渲染進程

瀏覽器包括4個進程:瀏覽器

  1. 主進程(Browser進程),瀏覽器只有一個主進程,負責資源下載,界面展現等主要基礎功能
  2. GPU進程,負責3D圖示繪製
  3. 第三方插件進程,負責第三方插件處理
  4. 渲染進程(Renderer進程),負責js執行,頁面渲染等功能,也是本章重點內容

渲染進程主要包括GUI渲染線程、Js引擎線程、事件循環線程、定時器線程、http異步線程。性能優化

GUI渲染線程

先看看瀏覽器獲得一個網站資源後幹了哪些事:網絡

  1. 首先瀏覽器會解析html代碼(實際上html代碼本質是字符串)轉化爲瀏覽器認識的節點,生成DOM樹,也就是DOM Tree
  2. 而後解析css,生成CSSOM(CSS規則樹)
  3. 把DOM Tree 和CSSOM結合,生成Rendering Tree(渲染樹)

GUI就是來幹這個事情的,若是修改了一些元素的顏色或者背景色,頁面就會重繪(Repaint),若是修改元素的尺寸,頁面就會迴流(Reflow),當頁面須要Repaing和Reflow時GUI多會執行,進行頁面繪製。異步

這裏提示一點:Reflow比Repaint的成本更高,在js性能優化中會將如何避免Reflow和Repaint函數

JS引擎線程

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引擎線程來執行。

Event Loop

上面介紹了渲染進程中的5個主要的線程,可能看完上面對各個線程簡單的介紹,還有點不明白他們之間到底怎麼協做工做的,下面就從Event Loop的角度來聊一聊他們之間是怎樣那麼愉快合做的。

已經知道了js是單線程運行的,也知道js中有同步操做和異步操做。同步和異步你們應該很熟了,很少介紹。

同步操做運行在js引擎線程(主線程)上,會造成一個執行棧,而異步操做則在他們對應的異步線程上處理(好比:定時操做在定時器線程上;http請求則在異步請求線程上處理)。

而事件循環線程則監視着這些異步線程們,等異步線程們裏面的操做有告終果(好比:定時器計時完成,或者http請求獲取到響應),便把他們的毀掉函數添加到事件隊列尾部,整個過程當中執行棧、事件隊列就構成Event Loop。

請看網絡盜圖:

clipboard.png

這是網絡上對Event Loop的解釋圖,相信你們如今能明白這張圖的含義了。

有關定時器(setTimeout、setInterval)的更多趣事

定時器會按照規定時間執行嗎?

定時器是規定在一段時間以後執行一段代碼,可是在js執行中不會準確無誤的按照預期的時間去執行定時器裏面的代碼。

一個緣由是W3C標準規定setTimeout中最小的時間週期是4毫秒,凡是低於4ms的時間間隔都按照4ms來處理。

其實還有一個重要的緣由,若是仔細看上面的文章,你們應該會想到在js執行的時候,主線程碰到定時器的時候,是不會直接處理的,應該是先把定時器事件交給定時器線程去處理,這時主線程繼續執行下面的代碼,同時定時器線程開始計時處理,等到計時完畢,事件循環線程會把定時器要執行的操做放在事件隊列末尾,等主線程空閒的時候再來執行事件隊列裏面的操做。

應該使用setTimeout仍是setInterval

使用setTimeout模擬setInterval代碼相似如下代碼:

var say = function() {
    setTimeout(say, 1000)
    console.log('hello world')
}

setTimeout(say, 1000)

這樣js碰到定時器,會交給定時器線程處理,而後等計時完畢,定時器裏面的操做添加到事件隊列,等主線程空閒去執行,主線程執行的時候又會發遇到定時器,這是又開始執行上面的一系列操做。

你會發現,這樣作會在每一次定時器執行完畢纔開始下一個定時器,其中的偏差只是等待主線程空閒所須要等待的時間。

而setInterval是規定每隔固定的時間就往定時器線程中推入一個事件,這樣作有一個問題,就是累積效應。

  • 累積效應:就是若是定時器裏面的代碼執行所需的時間大於定時器的執行週期,就會出現累計效應,簡單來講就是上一次定時器裏面的操做還沒執行完畢,下一次定時器事件又來了

累積效應會致使有些事件丟失,具體爲何會丟失,感興趣的能夠看這篇文章,因此爲了保險起見,儘可能去使用setTimeout而不使用setInterval。

若是有對setTimeout很是感興趣的同窗,我很是推薦你們去看看80% 應聘者都不及格的 JS 面試題這篇文章。

macrotask與microtask

microtask是Promise裏一個新的概念。

macrotask

  • macrotask中的事件都是放在一個事件隊列中的,而這個隊列由事件觸發線程維護
  • macrotask(又稱之爲宏任務),能夠理解是每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)
  • 每個task會從頭至尾將這個任務執行完畢,不會執行其它
  • 瀏覽器爲了可以使得JS內部task與DOM任務可以有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行從新渲染

microtask

  • microtask(又稱爲微任務),能夠理解是在當前 task 執行結束後當即執行的任務
  • microtask中的全部微任務都是添加到微任務隊列(Job Queues)中,等待當前macrotask執行完畢後執行,而這個隊列由JS引擎線程維護
  • 在當前task任務後,下一個task以前,在渲染以前執行
  • 因此它的響應速度相比setTimeout(setTimeout是task)會更快,由於無需等渲染
  • 也就是說,在某一個macrotask執行完後,就會將在它執行期間產生的全部microtask都執行完畢(在渲染前)

請看網絡盜圖:

clipboard.png

因此js運行過程:

  • 執行一個宏任務(棧中沒有就從事件隊列中獲取)
  • 執行過程當中若是遇到微任務,就將它添加到微任務的任務隊列中
  • 宏任務執行完畢後,當即執行當前微任務隊列中的全部微任務(依次執行)
  • 當前宏任務執行完畢,開始檢查渲染,而後GUI線程接管渲染
  • 渲染完畢後,JS線程繼續接管,開始下一個宏任務(從事件隊列中獲取)

有關macrotask和microtask的分析借鑑於從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理

相關文章
相關標籤/搜索