前言
對(duì)于不同的角色,瀏覽器有著不同的意義:
普通用戶通過(guò)它來(lái)瀏覽網(wǎng)頁(yè),在乎的是穩(wěn)定,高效和安全;
前端開(kāi)發(fā)同學(xué)關(guān)心的是兼容性和性能優(yōu)化。我們可以通過(guò)學(xué)習(xí)的內(nèi)部機(jī)制,更好的理解各種優(yōu)化手段。
泛泛的介紹意義不大,我們從快/穩(wěn)/省/安全這幾個(gè)維度切入,看看瀏覽器是如何做到這幾點(diǎn)的,這里以為例,畢竟開(kāi)源,且資料豐富。
快加載快
拿到頁(yè)面請(qǐng)求后,首先對(duì)需要請(qǐng)求的資源做分類,然后根據(jù)相關(guān)的安全策略,對(duì)資源做權(quán)限校驗(yàn),接著對(duì)加載順序按優(yōu)先級(jí)排序,再根據(jù)順序進(jìn)行加載。
支持的資源分為如下14類:
資源的權(quán)限校驗(yàn)這塊瀏覽器不支持flash什么意思,放到安全那一節(jié)介紹。
對(duì)資源的優(yōu)先級(jí)是這樣定義的:
可以看到優(yōu)先級(jí)總共分為五級(jí):very-high、high、、low、very-low,其中頁(yè)面、CSS、字體這三個(gè)的優(yōu)先級(jí)是最高的,然后就是、Ajax這種,圖片、音視頻的默認(rèn)優(yōu)先級(jí)是比較低的,最低的是的資源。
對(duì)于網(wǎng)頁(yè)來(lái)說(shuō),對(duì)資源按照優(yōu)先級(jí)來(lái)下載,與體驗(yàn)有很大的幫助。
(預(yù)加載)是個(gè)有意思的東西,有時(shí)候你可能需要讓一些資源先加載準(zhǔn)備好。
例如用戶輸入出錯(cuò)的時(shí)候在輸入框右邊顯示一個(gè)X的圖片,如果等要顯示的時(shí)候再去加載就會(huì)有延時(shí),這個(gè)時(shí)候可以用一個(gè)link標(biāo)簽:
瀏覽器空閑的時(shí)候就會(huì)去加載。另外還可以預(yù)解析DNS:
預(yù)建立TCP連接:
資源緩存
資源的緩存是瀏覽器優(yōu)化加載的重要手段。加載后的資源會(huì)存入資源緩存池,當(dāng)需要請(qǐng)求資源的時(shí)候,會(huì)優(yōu)先從緩存池中查找,查找的key就是資源的URL。
資源緩存池使用LRU算法管理其中的資源,如果是強(qiáng)緩存s,又能命中cache的話,則不發(fā)送請(qǐng)求,如果是協(xié)商緩存,則仍向服務(wù)端發(fā)送請(qǐng)求。
多進(jìn)程資源加載
采用的是多進(jìn)程資源加載的機(jī)制:
進(jìn)程(對(duì)應(yīng)的是tab)沒(méi)有權(quán)限下載資源,處于安全和效率的考量,進(jìn)程的資源獲取實(shí)際上是通過(guò)進(jìn)程間通信(IPC)交給進(jìn)程(對(duì)應(yīng)整個(gè)瀏覽器)來(lái)完成,進(jìn)程有獲取本地或網(wǎng)絡(luò)資源的權(quán)限。
這種架構(gòu)的一個(gè)優(yōu)點(diǎn)是,所有的資源請(qǐng)求都完全在I/O線程上處理,用戶接口產(chǎn)生的活動(dòng)與網(wǎng)絡(luò)事件之間互不干擾。資源過(guò)濾器運(yùn)行在進(jìn)程中的I/O線程中瀏覽器不支持flash什么意思,截獲資源請(qǐng)求消息,并將其轉(zhuǎn)發(fā)給進(jìn)程中的st單例。
這個(gè)單例接口允許瀏覽器控制各個(gè)進(jìn)程的網(wǎng)絡(luò)訪問(wèn)權(quán)限,它還能實(shí)現(xiàn)高效和一致性的資源共享,例如:
池和連接限制:瀏覽器能夠?qū)γ總€(gè)、代理和{, host, port}組所對(duì)應(yīng)的已開(kāi)啟數(shù)量進(jìn)行限制(分別為256、32和6個(gè))。注意,按照這個(gè)規(guī)則,同一{host, port}最多可以進(jìn)行6個(gè)HTTP和6個(gè)HTTPS連接。
重用:持久性的TCP連接會(huì)在請(qǐng)求處理之后在池中保留一段時(shí)間,以供連接重用,這樣能夠避免發(fā)起新的連接額外帶來(lái)的DNS、TCP和SSL(如有需要)的啟動(dòng)開(kāi)銷。
后期綁定:當(dāng)準(zhǔn)備好分派應(yīng)用程序請(qǐng)求時(shí),請(qǐng)求才與基礎(chǔ)的TCP連接綁定起來(lái),這樣一來(lái)可以獲得更好的請(qǐng)求次序優(yōu)化(例如:當(dāng)在連接中時(shí),更高優(yōu)先級(jí)的請(qǐng)求到達(dá)),更大的流量(例如:在現(xiàn)有可用而新連接正在打開(kāi)時(shí),重用“剛使用過(guò)”的TCP連接)以及TCP預(yù)連接的通用機(jī)制和其他一些優(yōu)化。
一致的會(huì)話狀態(tài):所有進(jìn)程的身份鑒證、和緩存數(shù)據(jù)都是共享的。
全局性資源和網(wǎng)絡(luò)優(yōu)化:瀏覽器可以從所有進(jìn)程和未完成請(qǐng)求的全局做出決策。例如,對(duì)前景標(biāo)簽頁(yè)發(fā)起的請(qǐng)求賦予網(wǎng)絡(luò)優(yōu)先級(jí)。
預(yù)測(cè)性優(yōu)化:通過(guò)觀測(cè)所有的網(wǎng)絡(luò)流量情況,能夠構(gòu)建和修正預(yù)測(cè)性模型提升性能。
就進(jìn)程而言,它只是通過(guò)IPC發(fā)送資源請(qǐng)求消息,這個(gè)請(qǐng)求被打上對(duì)應(yīng)進(jìn)程的唯一請(qǐng)求ID,剩下的部分都是由瀏覽器內(nèi)核進(jìn)程處理的。
啟動(dòng)快
影響頁(yè)面渲染效率的因素不少,對(duì)此做了不少優(yōu)化。
V8引擎的優(yōu)化
2017年,V8做了重大升級(jí),用上了(點(diǎn)火器) + (增壓渦輪)。
這樣做的目的一是降低內(nèi)存占用,二是減少啟動(dòng)時(shí)間,三是降低復(fù)雜度。
是一種技術(shù),會(huì)把頁(yè)面分成多層,開(kāi)啟多個(gè)線程,分別對(duì)它們進(jìn)行光柵化。當(dāng)頁(yè)面有滾動(dòng)發(fā)生時(shí),因?yàn)楦鲗右呀?jīng)做了光柵化,所以要做的就是合成新的幀。
為了找出哪些元素屬于哪一層,主線程會(huì)根據(jù) Tree生成Layer Tree。如果你希望某個(gè)元素分層渲染,可以通過(guò)設(shè)置will-這個(gè)樣式屬性,提醒瀏覽器這么做。
一旦創(chuàng)建了層樹(shù)并確定了繪制順序,主線程就會(huì)將該信息提交給(合成器)線程。線程隨后柵格化各層。一個(gè)圖層可能像頁(yè)面的整個(gè)長(zhǎng)度一樣大,因此合成器線程將它們分成多個(gè)圖塊并將每個(gè)圖塊發(fā)送到光柵線程。柵格線程?hào)鸥窕總€(gè)圖塊并將它們存儲(chǔ)在GPU內(nèi)存中。
線程為不同的圖塊分配不同的優(yōu)先級(jí),以便視口內(nèi)的部分優(yōu)先被光柵化。圖層還具有多個(gè)不同分辨率的拼接,以應(yīng)付放大縮小等操作。
使用GPU
利用 GPU 的最初的動(dòng)力是 3D CSS,除了給 3D CSS 帶來(lái)加速,GPU 同樣可以用于提升普通的頁(yè)面的渲染。這個(gè)理念在 中得到了實(shí)現(xiàn)。首先 將 GPU 加速運(yùn)用到了 video 等標(biāo)簽的渲染上,另外為高效合成 z 軸方向重疊元素,引入了圖像合成的概念。
利用 GPU 解決了這幾個(gè)影響性能的問(wèn)題:
把部分光柵化的任務(wù)交給 GPU,降低繪制使用的時(shí)間(每幀從 100ms 降低到 4-5ms),為 的執(zhí)行爭(zhēng)取了更多的時(shí)間
利用合成器,可以讓一些 CSS 動(dòng)畫完全在 GPU 中繪制( ),不需要 CPU 的干預(yù)(Main ),即便被 阻塞也能保持動(dòng)畫流暢
圖層之間互不影響,減少了發(fā)生 時(shí)所要遍歷的元素?cái)?shù)量
在其多進(jìn)程架構(gòu)上引入了 GPU 進(jìn)程。這個(gè)模型是可以伸縮的,在一些性能較低的平臺(tái)上,GPU 進(jìn)程可能會(huì)降為 GPU 線程。渲染進(jìn)程對(duì) GPU 的訪問(wèn),會(huì)以指令的形式發(fā)送到 (它是渲染進(jìn)程和 GPU 進(jìn)程共享的內(nèi)存區(qū)域),然后通過(guò) IPC 告知 GPU 進(jìn)程。大體來(lái)說(shuō),比起 IPC 帶來(lái)的損耗,這個(gè)架構(gòu)帶來(lái)的收益更加突出。因?yàn)榻^大部分指令不需要返回值,這讓渲染進(jìn)程可以立即返回,并繼續(xù)處理其他渲染任務(wù)。另外,將渲染進(jìn)程隔離在不能直接訪問(wèn) GPU 的安全沙盒中,這對(duì) 提供 擴(kuò)展的場(chǎng)景顯得尤其重要。
運(yùn)行流暢多線程模型
頁(yè)面運(yùn)行不流暢的表現(xiàn)主要是janky(跳幀),作為前端同學(xué),多少都知道一些關(guān)于如何避免跳幀,卡頓的優(yōu)化做法,當(dāng)然瀏覽器在這上面也做了不少改進(jìn)。
為了解決頁(yè)面卡頓的問(wèn)題,使用基于異步通信的多線程模型。也就是說(shuō),一個(gè)線程請(qǐng)求另外一個(gè)線程執(zhí)行一個(gè)任務(wù)的時(shí)候,不需要等待該任務(wù)完成就可以去做其它事情,以此避免卡頓。
事件的優(yōu)化
一般我們屏幕的刷新速率為 60fps,但某些事件的觸發(fā)會(huì)高于這個(gè)頻率,出于優(yōu)化的目的, 會(huì)合并連續(xù)的事件(如 wheel, , , , ),并延遲到下一幀渲染時(shí)候執(zhí)行。
而,keyup,,,和 這些非連續(xù)事件則會(huì)立即被觸發(fā)。
穩(wěn)多進(jìn)程模型
之前介紹多進(jìn)程資源加載的時(shí)候,簡(jiǎn)單提到過(guò)進(jìn)程和進(jìn)程,為了保證瀏覽器的穩(wěn)定性,采用多進(jìn)程的架構(gòu),將,和GPU進(jìn)程與進(jìn)程分隔開(kāi)來(lái),這幾個(gè)進(jìn)程引發(fā)的Crash獲取其他異常不會(huì)導(dǎo)致整個(gè)瀏覽器崩潰。
的四類主要進(jìn)程:
進(jìn)程
負(fù)責(zé)合成瀏覽器的UI,包括標(biāo)題欄、地址欄、工具欄以及各個(gè)TAB的網(wǎng)頁(yè)內(nèi)容。一個(gè)實(shí)例只有一個(gè)進(jìn)程。
進(jìn)程
負(fù)責(zé)解析和渲染網(wǎng)頁(yè)的內(nèi)容。一般來(lái)說(shuō),一個(gè)TAB就對(duì)應(yīng)有一個(gè)進(jìn)程。
我們也可以設(shè)置啟動(dòng)參數(shù),讓具有相同的域名的TAB都運(yùn)行在同一個(gè)進(jìn)程中。
無(wú)論是進(jìn)程,還是進(jìn)程,當(dāng)啟用了硬件加速渲染時(shí),它們都是通過(guò)GPU進(jìn)程來(lái)渲染UI的。不過(guò)進(jìn)程是將網(wǎng)頁(yè)內(nèi)容渲染在一個(gè)離屏窗口的,例如渲染在一個(gè)Frame 上,而進(jìn)程是直接將UI渲染在Frame 上,也就是屏幕上。
正因?yàn)槿绱耍M(jìn)程渲染好的網(wǎng)頁(yè)UI要經(jīng)過(guò)進(jìn)程合成之后,才能在屏幕上看到。
(題外話,說(shuō)起離屏窗口,想到了之前項(xiàng)目中的一個(gè)做法:在一個(gè)項(xiàng)目中,拖拽窗體時(shí),將窗體內(nèi)的元素扔到視口外,待拖拽停止后,再將它們放回來(lái))
進(jìn)程
用來(lái)運(yùn)行第三方開(kāi)發(fā)的,以便擴(kuò)展瀏覽器的功能。Flash就是一個(gè),它運(yùn)行在獨(dú)立的進(jìn)程中。
為了避免創(chuàng)建過(guò)多的進(jìn)程,同一個(gè)的不同實(shí)例都是運(yùn)行在同一個(gè)進(jìn)程中的。
也就是說(shuō),不管是在同一個(gè)TAB的網(wǎng)頁(yè)創(chuàng)建的同類,還是在不同TAB的網(wǎng)頁(yè)創(chuàng)建的同類,它們都是運(yùn)行在同一個(gè)進(jìn)程中。
GPU進(jìn)程
GPU進(jìn)程在前面介紹渲染時(shí)介紹過(guò)了,這里不再贅述。
省
在的官網(wǎng)文檔中,貌似沒(méi)有找到關(guān)于省電節(jié)能的內(nèi)容。從網(wǎng)上查到的內(nèi)容來(lái)看,似乎也是比較費(fèi)電的,據(jù)說(shuō)因?yàn)樯澈械南拗疲?dāng)播放視頻時(shí),只能使用GPU加速渲染,而不能加速解碼。不過(guò)這塊我并不了解,不做展開(kāi)。
安全
當(dāng)我們通過(guò)瀏覽器訪問(wèn)網(wǎng)頁(yè)的時(shí)候,瀏覽器需要保護(hù)我們數(shù)據(jù)的安全。像,賬號(hào)/密碼這類涉及個(gè)人隱私,交易支付的信息,不能被惡意網(wǎng)頁(yè)獲取。
同源策略
瀏覽器的安全模型中,域是十分重要的概念,域由協(xié)議+域名+端口構(gòu)成。不同域之間的資源訪問(wèn)受到嚴(yán)格控制。頁(yè)面的DOM,用戶數(shù)據(jù)和XHR都無(wú)法跨域訪問(wèn)。
CSP
實(shí)際情況中,同源策略對(duì)于很多XSS(跨站腳本攻擊)無(wú)能為力,因?yàn)椴簧儋Y源是可以跨域加載的(image,JS)。瀏覽器提供了CSP來(lái)防止XSS,使用HTTP消息頭來(lái)指定網(wǎng)頁(yè)允許哪些域中的哪些資源可以被加載。
CSP通過(guò)HTTP頭部由服務(wù)端制定,頭部類型由于歷史原因總共由三種,這三種僅僅是兼容性的差別,針對(duì)瀏覽器,我們僅需關(guān)注--頭部。CSP頭部的定義規(guī)則如下:
--: 名 值; 名 值; 名 值;
指令值的規(guī)范如下圖: