瀏覽器組成和各引擎工作原理


1.瀏覽器的主要構成部分

  • 1.用戶界面
  • 2.瀏覽器引擎(負責窗口管理、Tab進程管理等)
  • 3.渲染引擎(有叫內核,負責HTML解析、頁面渲染)
  • 4.JS引擎(JS解釋器,如Chrome和Nodejs采用的V8)

 

 

這里面最核心的就是渲染引擎和JS引擎,后面會詳細介紹這兩個引擎的相關內容。

常見瀏覽器的渲染引擎和JS引擎如下:

 

 注:新版本的Chrome采用的渲染引擎是Blink,Blink是由谷歌團隊從Webkit衍生開發出來的引擎,主要有應用到Chrome和Opera瀏覽器。

2.從進程和線程的角度來理解瀏覽器工作

1)進程和線程

  • 進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)
  • 線程是cpu調度的最小單位(線程是建立在進程的基礎上的一個程序運行單位,一個進程中可以有多個線程)

進程可以類比為工廠,線程就是工廠里面的工人,一個工廠可以包含一個或者多個工人,工人之間可以相互協作,並且共享工作空間

2)瀏覽器的多進程架構

現代的瀏覽器采用的都是多進程架構,主要包含以下三種進程:

1.Browser進程

瀏覽器的主線程,主要負責瀏覽器的頁面管理、書簽、前進后退、資源下載管理等,整個瀏覽器應用程序只有一個,對應上述瀏覽器組成中的瀏覽器引擎。

2.渲染進程

內核進程、負責頁面渲染、JS執行,對應的是上述的渲染引擎和JS引擎,一個瀏覽器可以包含多個渲染進程,每個Tab窗口頁對應一個渲染進程

3.GPU進程

負責GPU渲染,整個瀏覽器應用程序只有一個

4.插件進程

瀏覽器安裝的插件(擴展程序),每個插件會創建一個進程

 

 

當打開上面兩個Tab時,Chrome任務管理器截圖:主要包括

  • 1個瀏覽器進程
  • 1個GPU進程
  • 1個網絡進程
  • 2個渲染進程(對應一個Tab一個進程)
  • 4個擴展程序進程

 

 MAC的活動監視器中的chorme進程,可以看到所有的渲染、擴展、GPU、網絡進程都統一顯示為Google Chrome Helper。

 

 

這種多進程瀏覽器架構主要有如下優勢:

  • 1.避免單個頁面奔潰影響整個瀏覽器
  • 2.避免第三方插件奔潰影響整個瀏覽器
  • 3.充分利用多核優勢

3)瀏覽器的渲染進程

  • 瀏覽器有多個渲染進程、一個Tab頁面一個(相同的Tab頁面可能會被合並)
  • 一個渲染進程包含多個線程

一個渲染進程主要包括如下線程:

1.GUI線程(主要負責解析HTML、CSS和渲染頁面)

2.JS引擎線程(負責解析和執行JS代碼)

3.事件線程(控制事件循環)

4.定時器線程(處理定時器相關邏輯)

5.異步請求線程(發起Ajax時會生成該線程)

線程規則:

1.GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起,頁面的更新操作會等到JS引擎空閑時執行,涉及任務和微任務相關知識

2.一個渲染進程同時只有一個JS解析線程在運行

3.JS引擎線程不停的處理事件線程推送到事件隊列中的任務

4.定時器和異步請求最終生成的回調事件也有事件線程來控制和管理

了解了瀏覽器的渲染進程之后我們再來看看JS引擎。

4)從事件循環的角度來理解JS引擎的工作過程

在理解什么是事件循環之前,我們先來了解下同步和異步的概念

1.同步和異步

同步是代碼執行后就可以獲得想要的結果,異步是指代碼執行之后不能立即獲得結果,

你打電話問書店老板有沒有《Javascript權威指南》這本書,如果是同步通信機制,書店老板會說,你稍等,”我查一下",然后開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果)。
而異步機制,書店老板直接告訴你我查一下啊,查好了打電話給你,然后直接掛電話了(不返回結果)。然后查好了,他會主動打電話給你。在這里老板通過“回電”這種方式來返回結果。

.同步任務和異步任務

同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務;異步任務指的是,不進入主線程、而是由事件線程調度,在滿足執行條件的時候放到事件隊列中,等待主線程(JS線程)的執行。

3.為什么要有事件循環的概念

JS包含有異步操作(如:Ajax、定時器),這些異步操作完成之后需要通知JS引擎來處理異步操作的返回,如Ajax的Callback。這些異步操作什么時候完成是不確定的,所以需要有一個事件隊列,事件線程將已經完成的異步操作的回調任務加載到事件隊列中,JS引擎在執行完當前的同步任務之后循環從事件隊列中取事件執行。

異步任務:setTImeout、setInterval、Promise、process.nextTick(Node.js)、Ajax

同步任務:除以上異步任務

同步任務和異步任務的執行流程如下:

異步任務統一有事件線程管理,當異步任務完成的時候會被放入到事件隊列中,JS在順序執行完當前的代碼之后會從事件隊列中讀取任務,再重復整個流程,判斷該任務是同步還是異步。

 

 

4.異步任務的優先級

如果按照上述的簡化理解,所有異步任務都按照滿足執行條件的順序放到事件隊列中,世界很和平,先來先到,但是在ES6當中,引入了microtask的概念,microtask會在當前的任務執行完成之后立即執行。因為我們將異步任務分為task和microtask,我們又稱為宏任務和微任務。

task:setTImeout、setInterval、ajax

microtask:MutationObserve、promise、process.nextTick(Node.js)

這樣子加了優先級的話JS的執行又會變得再復雜一點,如下圖所示,異步任務執行完成之后會判斷他是task還是microtask,再分別加到不同的時間隊列中,JS當前任務執行完成之后優先清空當前的microtask隊列,而且在每次執行完宏任務的時候都會去清空微任務。

 

 

示例:

運行如下示例,就可以驗證上述執行流程

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="outer">
          <div class="inner"></div>
    </div>
    <script>
        // Let's get hold of those elements
        var outer = document.querySelector('.outer');
        var inner = document.querySelector('.inner');
        // Let's listen for attribute changes on the
        // outer element
        new MutationObserver(function() {
          console.log('mutate');
        }).observe(outer, {
          attributes: true
        });
        // Here's a click listener…
        function onClick() {
          console.log('click');
          setTimeout(function() {
            console.log('timeout');
          }, 0);
          Promise.resolve().then(function() {
            console.log('promise');
          });
          outer.setAttribute('data-random', Math.random());
        }
        // …which we'll attach to both elements
        inner.addEventListener('click', onClick);
        outer.addEventListener('click', onClick);
    </script>
</body>
</html>

5.UI渲染線程什么時候工作

UI渲染線程會在當前的Task執行完成之后,下一個Task執行之前執行,微任務會優先於UI渲染線程,這就意味着我們使用微任務更新的DOM能更快的被渲染出來。另外Vue.js最新版本數據變更的時候采用的是promise和MutationObserver創建微任務:https://github.com/vuejs/vue/...

Demo:用於理解任務和UI渲染之間的關系

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue nextTick</title>
</head>
<body>
    <!-- jsFiddle例子:http://jsfiddle.net/gkmzns9u/14/ -->
    <div id="task">
        <div id="msg">
            {{msg}}
        </div>
        <div @click="greet">
            click me!
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script type="text/javascript">
        const vm = new Vue({
            el: '#task',
            data: {
                msg: 'hello'
            },
            methods: {
                greet: () => {
                    // 修改model,觸發set方法,調用update方法,添加DOM更新微任務
                      vm.msg  = 'hello world';
                      vm.msg  = 'hello world2';
                      // 查看DOM,由於是異步更新DOM,根據EventLoop原理可知,這里DOM還沒有更新,
                      // hello
                    alert(document.getElementById('msg').innerHTML);
                    // nextTick使用promise,是個微任務,在當前greet方法執行完成之后會立即執行
                    vm.$nextTick().then(() => {
                        // 由於DOM更新微任務先被添加,先入先出,這里獲取的DOM已經是更新好的
                        // hello world
                        alert(document.getElementById('msg').innerHTML);
                        // 直接修改DOM,同步任務
                        document.getElementById('msg').innerHTML = 'test'
                        // 立即生效
                        // test
                        alert(document.getElementById('msg').innerHTML);
                        /* 根據HTML Standard,一輪事件循環執行結束(包括微任務)之后,下輪事件循環執行之前開始進行UI render。即:macro-task任務執行完畢,接着執行完所有的micro-task任務后,此時本輪循環結束,開始執行UI render。UI render完畢之后接着下一輪循環。 */
                    });
                    // 由於setTimeout為宏任務,雖然延遲時間為0,但還是要晚於nextTick執行
                    // 而且可以明顯看到在setTimeout回調執行之前頁面上已經渲染上test,說明UI Render已經在setTimeout回調之前執行
                    setTimeout(()=>{
                        alert('setTimeout start')
                        document.getElementById('msg').innerHTML = 'setTimeout'
                    }, 0)
                }
            }
        });
    </script>
</body>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM