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>