大话Chrome浏览器原理
一、一個(gè)頁(yè)面為什么4個(gè)進(jìn)程?
(1)主要原因
- 進(jìn)程中的任何一個(gè)線(xiàn)程崩潰都會(huì)導(dǎo)致整個(gè)進(jìn)程崩潰。
- 線(xiàn)程之間的數(shù)據(jù)時(shí)共享的,多頁(yè)面使用多線(xiàn)程有安全性問(wèn)題。
- 當(dāng)一個(gè)進(jìn)程關(guān)閉后資源的回收時(shí)候操作系統(tǒng)控制的,不易出現(xiàn)內(nèi)存泄漏。
- 插件的崩潰會(huì)導(dǎo)致Chrome的不穩(wěn)定。
- 所有模塊都在一個(gè)進(jìn)程導(dǎo)致Chrome不流暢。
(2)目前Chrome的進(jìn)程架構(gòu)
- 瀏覽器進(jìn)程:主要負(fù)責(zé)用戶(hù)界面顯示、交互、子進(jìn)程管理、存儲(chǔ)。
- 渲染進(jìn)程:使用Blink排版引擎和V8引擎渲染出頁(yè)面,Chrome會(huì)為每一個(gè)Tab創(chuàng)建一個(gè)渲染進(jìn)程,每個(gè)進(jìn)程運(yùn)行在沙箱中。
- GPU進(jìn)程:初衷是實(shí)現(xiàn)CSS 3D、網(wǎng)頁(yè)繪制和Chrome的UI部分。
- 網(wǎng)絡(luò)進(jìn)程:加載網(wǎng)絡(luò)資源。
- 插件進(jìn)程:負(fù)責(zé)插件的運(yùn)行。
(3)當(dāng)前的Chrome架構(gòu)帶來(lái)的問(wèn)題
- 消耗資源
- 體系復(fù)雜
(4)未來(lái)面向服務(wù)的架構(gòu)(SOA)
構(gòu)建一個(gè)更加內(nèi)聚、松耦合、易于維護(hù)和擴(kuò)展的系統(tǒng)
Chrome正在構(gòu)建操作系統(tǒng)化的Chrome基礎(chǔ)服務(wù),在性能強(qiáng)大的設(shè)備上使用多進(jìn)程的方式運(yùn)行基礎(chǔ)服務(wù),當(dāng)在硬件資源受限的設(shè)備上使用Chrome的時(shí)候,就使用單進(jìn)程的方式。
二、TCP協(xié)議如何保證頁(yè)面?zhèn)魉偷綖g覽器?
從TCP和UDP協(xié)議的傳輸過(guò)程思考,引出QUIC和HTTP3。
三、為什么第二次打開(kāi)站點(diǎn)會(huì)很快?
瀏覽器發(fā)起HTTP請(qǐng)求的流程:
- 構(gòu)建請(qǐng)求,首先是構(gòu)建請(qǐng)求行。
- 查找緩存,查找瀏覽器緩存失敗才會(huì)進(jìn)行網(wǎng)絡(luò)請(qǐng)求。
- 通過(guò)DNS準(zhǔn)備IP地址和端口。
- 等待TCP隊(duì)列,Chrome機(jī)制是同一個(gè)域名下最多只能建立6個(gè)連接,否則就會(huì)進(jìn)入等待TCP隊(duì)列。
- 建立TCP連接。
- 發(fā)送HTTP請(qǐng)求。
- 首先發(fā)送請(qǐng)求行,分別是請(qǐng)求方法、請(qǐng)求URI、HTTP協(xié)議版本。
- 如果是POST請(qǐng)求,那么還要發(fā)送請(qǐng)求體。
- 然后服務(wù)器返回?cái)?shù)據(jù),包括響應(yīng)頭和響應(yīng)體。
- 通常情況下會(huì)斷開(kāi)TCP連接,如果在HTTP頭部加入Connection:Keep-Alive,這樣TCP就不會(huì)斷開(kāi),連接可以被復(fù)用。
四、從輸入U(xiǎn)RL到頁(yè)面展示,中間發(fā)生了什么?
- 用戶(hù)向?yàn)g覽器輸入U(xiǎn)RL,然后瀏覽器處理用戶(hù)輸入,判斷是合法的URL地址還是搜索關(guān)鍵字,如果是關(guān)鍵字那么使用默認(rèn)的搜索引擎構(gòu)建搜索URL。
- 檢查是否有緩存內(nèi)容,否則瀏覽器通過(guò)進(jìn)程間的IPC通信向網(wǎng)絡(luò)進(jìn)程發(fā)送請(qǐng)求信息。
- 網(wǎng)絡(luò)進(jìn)程接收服務(wù)器返回的數(shù)據(jù),根據(jù)狀態(tài)碼判斷是否進(jìn)行重定向等操作。如果是301或302,那么表示需要重定向,此時(shí)網(wǎng)絡(luò)進(jìn)程會(huì)在請(qǐng)求頭的Location字段中讀取重定向的地址,再次發(fā)送網(wǎng)絡(luò)請(qǐng)求。
- 通過(guò)響應(yīng)頭中的Content-Type判斷進(jìn)行何種操作。如果是下載類(lèi)型,那么網(wǎng)絡(luò)進(jìn)程會(huì)把任務(wù)交給下載任務(wù)管理器。
- 由于服務(wù)器響應(yīng)數(shù)據(jù)的時(shí)候就已經(jīng)準(zhǔn)備好了渲染進(jìn)程,那么會(huì)將數(shù)據(jù)提交給渲染進(jìn)程。Chrome會(huì)為每個(gè)站點(diǎn)打開(kāi)一個(gè)渲染進(jìn)程。
五、JavaScript、HTML和CSS如何變成頁(yè)面?
由于渲染機(jī)制的復(fù)雜性,所以劃分為許多子階段,每個(gè)子階段都有輸入、輸出和處理過(guò)程,這許多子階段構(gòu)成了渲染流水線(xiàn)。
(1)構(gòu)建DOM
將HTML標(biāo)簽轉(zhuǎn)化為DOM樹(shù),每個(gè)節(jié)點(diǎn)對(duì)應(yīng)一個(gè)HTML標(biāo)簽。可以通過(guò)如下代碼從Chrome開(kāi)發(fā)者工具中獲取當(dāng)前頁(yè)面的DOM樹(shù):
documentHTML解析器會(huì)隨著文檔的加載,邊加載邊解析。HTML解析器維護(hù)了一個(gè)Token棧結(jié)構(gòu)用于計(jì)算父子節(jié)點(diǎn)之間的關(guān)系。
當(dāng)解析到JavaScript的時(shí)候,DOM解析將會(huì)停止,執(zhí)行代碼,因?yàn)镴avaScript有可能修改DOM結(jié)構(gòu)。
Chrome在解析之前會(huì)有預(yù)解析線(xiàn)程先下載文檔內(nèi)嵌入的JavaScript下載鏈接。
在解析JavaScript之前首先要解析CSS文件,CSS文件加載會(huì)阻塞JavaScript腳本執(zhí)行。
(2)樣式計(jì)算
- 解析CSS文件。當(dāng)渲染引擎接收到一個(gè)CSS文件的時(shí)候,會(huì)將CSS文本轉(zhuǎn)換為樣式表結(jié)構(gòu)。可以通過(guò)如下代碼在Chrome開(kāi)發(fā)者工具中獲取當(dāng)前頁(yè)面的樣式表:
- 轉(zhuǎn)換CSS屬性值,使其標(biāo)準(zhǔn)化。比如2em會(huì)被轉(zhuǎn)換為35px,HTML顏色會(huì)被轉(zhuǎn)化RGB顏色。
- 計(jì)算出DOM樹(shù)每個(gè)節(jié)點(diǎn)的樣式。CSS具有繼承和層疊規(guī)則。這些會(huì)在Chrome的Computed標(biāo)簽中顯示。
(3)布局節(jié)點(diǎn)
- 創(chuàng)建布局樹(shù)。由于HTML中還包含了許多不可見(jiàn)的元素,因此還需要?jiǎng)?chuàng)建一棵只包含可見(jiàn)元素的布局樹(shù)。
- 然后將可見(jiàn)布局樹(shù)和Computed CSS合成帶有CSS的DOM樹(shù)。
(4)圖層樹(shù)
渲染引擎還需要為特定的節(jié)點(diǎn)生成專(zhuān)門(mén)的圖層,并生成一棵圖層樹(shù)。可以在Chrome的Layers標(biāo)簽中查看。
并不是每一個(gè)節(jié)點(diǎn)都會(huì)對(duì)應(yīng)一個(gè)圖層,如果一個(gè)節(jié)點(diǎn)沒(méi)有圖層,那么這個(gè)節(jié)點(diǎn)就屬于父節(jié)點(diǎn)的圖層。
- 擁有層疊上下文屬性的HTML元素會(huì)提升為一個(gè)圖層。
- 需要進(jìn)行裁剪的HTML元素,比如overflow屬性。
(5)圖層繪制
渲染引擎會(huì)將圖層樹(shù)中的每個(gè)圖層進(jìn)行繪制,首先會(huì)將每一層的繪制拆分成許多繪制指令,然后繪制指令按照順序組成待繪制列表。
(6)柵格化操作
-
主線(xiàn)程將待繪制列表準(zhǔn)備好后提交給合成線(xiàn)程。通常情況下,一個(gè)頁(yè)面可能很大,但是視口ViewPort是有限大的。因此合成線(xiàn)程會(huì)將圖層劃分為圖塊,通常是256*256或者512*512。
- 合成線(xiàn)程會(huì)將視口附近的圖塊來(lái)優(yōu)先生成位圖,實(shí)際生成位圖的操作由柵格化線(xiàn)程來(lái)執(zhí)行。
- 柵格化線(xiàn)程通常情況下會(huì)使用GPU來(lái)完成,也叫快速柵格化。
- GPU生成的位圖保存在GPU的顯存中。瀏覽器中有個(gè)viz組件用來(lái)接收合成線(xiàn)程的DrawQuad命令,然后瀏覽器根據(jù)該命令將頁(yè)面內(nèi)容顯示在屏幕上。
(7)3個(gè)重要概念
- 重排:更新元素的幾何屬性。通過(guò)CSS或者JS修改了元素的位置屬性,那么就會(huì)觸發(fā)瀏覽器重新布局,導(dǎo)致需要完整的渲染流水線(xiàn)。
- 重繪:更新元素的繪制屬性。如果更改了頁(yè)面的顏色屬性,那么就會(huì)省去布局和分層階段。
- 合成:比如使用了CSS的transform屬性,那么就會(huì)避開(kāi)重繪和重排。
六、JavaScript是按照順序執(zhí)行的嗎?
變量提升:JavaScript解析引擎執(zhí)行代碼過(guò)程中,將變量的聲明部分和函數(shù)的聲明部分提升到代碼開(kāi)頭的行為,變量提升以后會(huì)給變量設(shè)置默認(rèn)值,這個(gè)默認(rèn)是就是undefined。
變量提升發(fā)生在編譯階段,在這個(gè)階段會(huì)生成執(zhí)行上下文和可執(zhí)行代碼。在執(zhí)行上下文中保存了變量環(huán)境對(duì)象,該對(duì)象保存了變量提升的內(nèi)容。
如果函數(shù)或者變量出現(xiàn)了重名,那么變量環(huán)境對(duì)象將會(huì)發(fā)生覆蓋。
console.log(x); var x = 10; var x = 20;f1(); function f1() {console.log('method: f1');} function f1() {console.log('method: f1 override');}f2(); var f2 = function() {console.log('method: f2');} var f2 = function() {console.log('method: f2 override');} undefined method: f1 override /Users/koils/test.js:9 f2(); ^TypeError: f2 is not a functio七、為什么JavaScript會(huì)出現(xiàn)棧溢出?
在JavaScript中每個(gè)函數(shù)都有自己的執(zhí)行上下文,JavaScript使用調(diào)用棧來(lái)管理這些執(zhí)行上下文環(huán)境。全局執(zhí)行上下文位于棧底。
調(diào)用棧是JavaScript引擎追蹤函數(shù)執(zhí)行的一個(gè)機(jī)制。
棧溢出:棧是有大小的,當(dāng)入棧數(shù)目超過(guò)這個(gè)大小就會(huì)造成棧溢出現(xiàn)象。
八、作用域、作用域鏈和閉包
ES6中通過(guò)引入塊級(jí)作用域配合let和const來(lái)避免變量提升這個(gè)設(shè)計(jì)缺陷。
作用域:是指在程序中定義變量的區(qū)域,這個(gè)位置決定了變量的生命周期。作用域是變量和函數(shù)的可訪(fǎng)問(wèn)范圍。在ES6之前只有全局作用域和函數(shù)作用域,之后支持塊級(jí)作用域。
變量提升帶來(lái)的問(wèn)題:
- 變量容易在不被察覺(jué)的情況下被覆蓋掉。
- 本來(lái)應(yīng)該銷(xiāo)毀的變量沒(méi)有被銷(xiāo)毀。
JavaScript如何支持塊級(jí)作用域?通過(guò)let聲明的變量在編譯階段會(huì)被存放到詞法環(huán)境,因此是使用詞法環(huán)境和棧來(lái)支持的。
在JavaScript的每個(gè)執(zhí)行上下文中都包含一個(gè)叫做outer的外部引用,用來(lái)指向外部的執(zhí)行上下文。當(dāng)進(jìn)行變量查找的時(shí)候在當(dāng)前作用域找不到就回去outer中查找,直到找到。這個(gè)查找鏈條叫做作用域鏈。
詞法作用域:作用域由代碼中的函數(shù)聲明的位置來(lái)決定,是靜態(tài)的作用域,通過(guò)這個(gè)作用域可以預(yù)測(cè)代碼的執(zhí)行過(guò)程。詞法作用域在代碼階段就決定好了,與函數(shù)如何調(diào)用無(wú)關(guān)。
閉包:在JavaScript中,根據(jù)詞法作用域的規(guī)則,內(nèi)部函數(shù)總是可以訪(fǎng)問(wèn)其外部函數(shù)聲明的變量,當(dāng)通過(guò)調(diào)用一個(gè)外部函數(shù)返回一個(gè)內(nèi)部函數(shù)的時(shí)候,即使該外部函數(shù)已經(jīng)執(zhí)行結(jié)束,但是內(nèi)部函數(shù)引用外部函數(shù)變量依然保存在內(nèi)存中,就把這些變量的集合稱(chēng)為閉包。
閉包的回收:如果閉包會(huì)一直被使用,那么可以當(dāng)做全局變量存在。但是如果使用頻率不高,而且占用內(nèi)存較大,盡量讓該閉包作為局部變量。
九、this
(1)全局執(zhí)行上下文的this
全局執(zhí)行上下文的this指向window對(duì)象。
(2)函數(shù)執(zhí)行上下文的this
默認(rèn)情況下也是指向window對(duì)象。設(shè)置this指向的方法有三種:
-
call方法,bind方法和apply方法。
-
通過(guò)對(duì)象調(diào)用位置:使用對(duì)象調(diào)用其內(nèi)部的一個(gè)方法,該方法的this是指向?qū)ο蟊旧淼摹?/p> var object = { fn: function () {console.log(this);} }; object.fn();
-
通過(guò)構(gòu)造函數(shù):函數(shù)中的this屬于新對(duì)象
function fn () {this.x = 'HelloWorld';} var object = new fn();
(3)this的設(shè)計(jì)缺陷
-
嵌套函數(shù)中this不會(huì)從外層函數(shù)中繼承,解決方法有
- 將this體系轉(zhuǎn)化為作用域體系
- 使用ES6中的箭頭函數(shù)
- 普通函數(shù)的this指向window對(duì)象,這個(gè)問(wèn)題可以通過(guò)使用嚴(yán)格模式解決。
十、JavaScript的內(nèi)存機(jī)制
(1)數(shù)據(jù)存儲(chǔ)
- 原始類(lèi)型
| Boolean | 只有true和false兩個(gè)值 |
| Null | 只有一個(gè)值null,使用typeof檢測(cè)時(shí)會(huì)返回object類(lèi)型,這是JavaScript的Bug |
| undefined | 一個(gè)沒(méi)有被賦值的默認(rèn)值,變量提升時(shí)也會(huì)使用該值 |
| Number | 數(shù)字類(lèi)型,64位二進(jìn)制格式 |
| BigInt | 可以用于表示任何精度 |
| String | 表示文本數(shù)據(jù),不可變 |
| Symbol | 唯一且不可修改,通常用于作為Object和Key |
| Object | 一組屬性的集合 |
- 引用類(lèi)型
JavaScript的內(nèi)存空間分為棧空間、堆空間和代碼空間。棧空間用于存儲(chǔ)執(zhí)行上下文。在JavaScript的賦值過(guò)程中,引用類(lèi)型只會(huì)復(fù)制內(nèi)存地址。
(2)垃圾回收
-
調(diào)用棧中的垃圾回收
JavaScript引擎通過(guò)向下移動(dòng)ESP來(lái)銷(xiāo)毀該函數(shù)保存在棧中執(zhí)行的上下文。
-
堆中的垃圾回收
-
JavaScript使用垃圾回收器收集垃圾。待際假說(shuō):大部分對(duì)象在內(nèi)存中存活時(shí)間會(huì)很短,不死的對(duì)象會(huì)活的更久。在V8引擎中分為新生代和老年代,新生代通常是1-8MB的內(nèi)存空間,并且兩個(gè)區(qū)域使用不同的GC機(jī)制。
- 新生代使用Scavenge算法,它將新生代劃分為兩個(gè)區(qū)域,一半是對(duì)象區(qū)域,另一半是空閑區(qū)域。當(dāng)對(duì)象區(qū)域?qū)憹M(mǎn)以后就進(jìn)行GC,首先對(duì)對(duì)象區(qū)域的對(duì)象進(jìn)行標(biāo)記,然后再清理垃圾,副垃圾收集器將這些沒(méi)有變成垃圾的對(duì)象復(fù)制到空閑區(qū)域,然后有序的排列,最后將對(duì)象區(qū)域和空閑區(qū)域進(jìn)行角色翻轉(zhuǎn)。
- JavaScript的主垃圾回收器主要進(jìn)行老年代的垃圾回收工作,使用標(biāo)記-清除算法。
- 當(dāng)JavaScript的進(jìn)行GC的時(shí)候,會(huì)產(chǎn)生StopTheWorld(全停頓)現(xiàn)象。由于老年代受到GC全停頓的影響較大,因此老年代的垃圾回收使用增量-標(biāo)記算法,使得JavaScript腳本的執(zhí)行和GC兩個(gè)線(xiàn)程交替執(zhí)行。
-
(3)解釋編譯
在JavaScript的執(zhí)行引擎V8中,既有解釋器(Ignition)也存在編譯器(TurboFan)。
- 首先會(huì)從JavaScript代碼翻譯為AST并生成執(zhí)行上下文。AST是?常重要的?種數(shù)據(jù)結(jié)構(gòu),在很多項(xiàng)?中有著?泛的應(yīng)?。其中最著名的?個(gè)項(xiàng)?是Babel。的?作原理就是先將ES6源碼轉(zhuǎn)換為AST,然后再將ES6語(yǔ)法的AST 轉(zhuǎn)換為ES5語(yǔ)法的AST,最后利?ES5的AST?成JavaScript源代碼。
- 詞法分析,生成Token。
- 語(yǔ)法分析,解析Token生成AST。
- 生成字節(jié)碼。解釋器根據(jù)AST解釋并執(zhí)行字節(jié)碼。字節(jié)碼是介于AST和機(jī)器碼之間的一種代碼,與特定類(lèi)型的機(jī)器無(wú)關(guān)。
- 執(zhí)行代碼。多次重復(fù)執(zhí)行的代碼會(huì)選定為熱點(diǎn)代碼,由編譯器編譯為機(jī)器代碼并保存。解釋器Ignition在解釋執(zhí)?字節(jié)碼 的同時(shí),收集代碼信息,當(dāng)它發(fā)現(xiàn)某?部分代碼變熱了之后,TurboFan編譯器把熱點(diǎn)的字節(jié) 碼轉(zhuǎn)換為機(jī)器碼,并把轉(zhuǎn)換后的機(jī)器碼保存起來(lái),以備下次使?,這叫做JIT即時(shí)編譯。
十一、消息隊(duì)列和事件循環(huán)
- Chrome將事件存放到隊(duì)列,然后使用循環(huán)機(jī)制將消息取出,然后執(zhí)行。比如渲染進(jìn)程專(zhuān)門(mén)有一個(gè)IO線(xiàn)程用于通過(guò)隊(duì)列接受其他線(xiàn)程傳來(lái)的任務(wù)。
- 當(dāng)線(xiàn)程需要安全的退出的時(shí)候,由于在進(jìn)程中設(shè)置了退出標(biāo)志,每次在隊(duì)列中取出任務(wù)執(zhí)行之前都需要檢查標(biāo)志。
- 對(duì)于高優(yōu)先級(jí)任務(wù)的處理,比如監(jiān)聽(tīng)DOM節(jié)點(diǎn)的變化情況,會(huì)作為微任務(wù)添加到隊(duì)列中宏任務(wù)的微任務(wù)隊(duì)列中,當(dāng)任務(wù)執(zhí)行完成后檢查當(dāng)前任務(wù)的微任務(wù)隊(duì)列是否存在微任務(wù),有就取出來(lái)執(zhí)行。
- 通過(guò)Promise和MutationObserver監(jiān)控某個(gè)DOM節(jié)點(diǎn)都會(huì)產(chǎn)生微任務(wù)。
十二、JavaScript面向?qū)ο?/h1> (1)封裝
由于JavaScript沒(méi)有提供權(quán)限訪(fǎng)問(wèn)修飾符,因此可以通過(guò)閉包的方式實(shí)現(xiàn)私有變量的保護(hù):
function Book(name) {this.getName = () => {return name;}this.setName = (x) => {name = x;} }let book = new Book("HelloWorld"); book.setName("JavaScript"); book.getName();(2)繼承
在ES6之前,沒(méi)有extends關(guān)鍵字,最常見(jiàn)的叫做原型鏈繼承。原型prototype是JavaScript函數(shù)中的一個(gè)內(nèi)置屬性,指向另外一個(gè)對(duì)象,被指向的對(duì)象的所有的屬性和方法都會(huì)被當(dāng)前的實(shí)例所繼承。
- 設(shè)置prototype的代碼需要放到構(gòu)造器之外。
- 設(shè)置prototype的代碼需要放到任何實(shí)例化之前。
原型鏈繼承無(wú)法解決父類(lèi)構(gòu)造方法存在參數(shù)的問(wèn)題,因此可以通過(guò)構(gòu)造繼承解決:
function Base1(name) {this.name = name;}function Base2(age) {this.age = age;}function Child(name, age) {Base1.call(this, name);Base2.call(this, age); }(3)多態(tài)
- 當(dāng)創(chuàng)建類(lèi)的實(shí)例的時(shí)候,沒(méi)有使用new關(guān)鍵字,this指的是window對(duì)象,否則指向的是當(dāng)前實(shí)例對(duì)象。
- 當(dāng)類(lèi)存在return語(yǔ)句的時(shí)候,如果返回的是基本數(shù)據(jù)類(lèi)型,那么this就會(huì)強(qiáng)制指定為當(dāng)前類(lèi)對(duì)象;如果返回的是引用數(shù)據(jù)類(lèi)型,那么會(huì)遵循return語(yǔ)句。
十三、setTimeout實(shí)現(xiàn)原理
Chrome中使用延遲隊(duì)列保存Chrome內(nèi)部的延時(shí)任務(wù)和setTimeout提交的延時(shí)任務(wù)。當(dāng)執(zhí)行完消息隊(duì)列中的任務(wù)之后就會(huì)開(kāi)始執(zhí)行延時(shí)隊(duì)列的處理函數(shù),然后延時(shí)隊(duì)列處理函數(shù)會(huì)根據(jù)發(fā)起時(shí)間和延遲時(shí)間計(jì)算出到期任務(wù)。
使用setTimeout的注意事項(xiàng):
-
如果當(dāng)前任務(wù)執(zhí)行時(shí)間過(guò)久,會(huì)影響到定時(shí)器的執(zhí)行。
-
如果setTimout存在嵌套,那么系統(tǒng)會(huì)設(shè)置4ms的間隔時(shí)間。
-
當(dāng)前頁(yè)面標(biāo)簽如果沒(méi)有被激活,那么setTimeout的執(zhí)行最小時(shí)間間隔是1s。目的是優(yōu)化加載消耗和耗電量。
-
延遲執(zhí)行時(shí)間有最大值,當(dāng)延時(shí)24.8天setTimeout就會(huì)溢出,因?yàn)閟etTimeout使用的是32bit來(lái)存儲(chǔ)。
-
setTimeout執(zhí)行的函數(shù)this對(duì)象指向window,可以通過(guò)匿名函數(shù)或者bind方法解決:
setTimeout(function() {}, 1); setTimeout(() => {}, 1);setTimeout(object.func.bind(object), 1);
十四、瀏覽器緩存
(1)強(qiáng)緩存與協(xié)商緩存
在瀏覽器中分為強(qiáng)緩存和協(xié)商緩存。強(qiáng)緩存不需要發(fā)送HTTP請(qǐng)求,當(dāng)檢查是否是強(qiáng)緩存的時(shí)候在HTTP1.0和HTTP1.1中是不一樣的:
- 早期的HTTP1.0使用的是Expires字段,它指明了過(guò)期時(shí)間。
- HTTP1.1使用的是Cache-Control字段,有以下參數(shù):
- 通過(guò)max-age指明緩存存活時(shí)間。
- private表示只有瀏覽器才能緩存,中間代理服務(wù)器無(wú)法緩存。
- no-cache表示跳過(guò)強(qiáng)緩存,直接進(jìn)入?yún)f(xié)商緩存階段
- no-store表示直接不進(jìn)行緩存
- s-maxage表示針對(duì)代理服務(wù)器的緩存時(shí)長(zhǎng)
- Expires和Cache-Control同時(shí)存在的時(shí)候,優(yōu)先考慮Cache-Control
當(dāng)強(qiáng)緩存失效之后,瀏覽器在請(qǐng)求頭中攜帶相應(yīng)的緩存tag來(lái)向服務(wù)器發(fā)請(qǐng)求,由服務(wù)器根據(jù)這個(gè)tag,來(lái)決定是否使用緩存,這就是協(xié)商緩存。
- Last-Modified:即最后修改時(shí)間。在瀏覽器第一次給服務(wù)器發(fā)送請(qǐng)求后,服務(wù)器會(huì)在響應(yīng)頭中加上這個(gè)字段。瀏覽器接收到后,如果再次請(qǐng)求,會(huì)在請(qǐng)求頭中攜帶If-Modified-Since字段,這個(gè)字段的值也就是服務(wù)器傳來(lái)的最后修改時(shí)間。服務(wù)器拿到請(qǐng)求頭中的If-Modified-Since的字段后,其實(shí)會(huì)和這個(gè)服務(wù)器中Last-Modified對(duì)比:
- 如果請(qǐng)求頭中的這個(gè)值小于最后修改時(shí)間,說(shuō)明是時(shí)候更新了。返回新的資源,跟常規(guī)的HTTP請(qǐng)求響應(yīng)的流程一樣。
- 否則返回304,告訴瀏覽器直接用緩存。
- ETag:ETag 是服務(wù)器根據(jù)當(dāng)前文件的內(nèi)容,給文件生成的唯一標(biāo)識(shí),只要里面的內(nèi)容有改動(dòng),這個(gè)值就會(huì)變。服務(wù)器通過(guò)響應(yīng)頭把這個(gè)值給瀏覽器。瀏覽器接收到ETag的值,會(huì)在下次請(qǐng)求時(shí),將這個(gè)值作為If-None-Match這個(gè)字段的內(nèi)容,并放到請(qǐng)求頭中,然后發(fā)給服務(wù)器。服務(wù)器接收到If-None-Match后,會(huì)跟服務(wù)器上該資源的ETag進(jìn)行比對(duì):
- 如果兩者不一樣,說(shuō)明要更新了。返回新的資源,跟常規(guī)的HTTP請(qǐng)求響應(yīng)的流程一樣。
- 否則返回304,告訴瀏覽器直接用緩存。
在精準(zhǔn)度上,ETag優(yōu)于Last-Modified。優(yōu)于 ETag 是按照內(nèi)容給資源上標(biāo)識(shí),因此能準(zhǔn)確感知資源的變化。Last-Modified 能夠感知的單位時(shí)間是秒,如果文件在 1 秒內(nèi)改變了多次,那么這時(shí)候的 Last-Modified 并沒(méi)有體現(xiàn)出修改了。
在性能上,Last-Modified優(yōu)于ETag,也很簡(jiǎn)單理解,Last-Modified僅僅只是記錄一個(gè)時(shí)間點(diǎn),而 Etag需要根據(jù)文件的具體內(nèi)容生成哈希值。
(2)Service Worker Cache
Service Worker 借鑒了 Web Worker的 思路,即讓 JS 運(yùn)行在主線(xiàn)程之外,由于它脫離了瀏覽器的窗體,因此無(wú)法直接訪(fǎng)問(wèn)DOM。雖然如此,但它仍然能幫助我們完成很多有用的功能,比如離線(xiàn)緩存、消息推送和網(wǎng)絡(luò)代理等功能。其中的離線(xiàn)緩存就是 Service Worker Cache。Service Worker 同時(shí)也是 PWA 的重要實(shí)現(xiàn)機(jī)制。
(3)Memory Cache
內(nèi)存緩存,從效率上講它是最快的。但是從存活時(shí)間來(lái)講又是最短的,當(dāng)渲染進(jìn)程結(jié)束后,內(nèi)存緩存也就不存在了。
(4)Disk Cache
存儲(chǔ)在磁盤(pán)中的緩存,從存取效率上講是比內(nèi)存緩存慢的,但是他的優(yōu)勢(shì)在于存儲(chǔ)容量和存儲(chǔ)時(shí)長(zhǎng)。
(5)Push Cache
即推送緩存,這是瀏覽器緩存的最后一道防線(xiàn)。它是 HTTP/2中的內(nèi)容,雖然現(xiàn)在應(yīng)用的并不廣泛,但隨著 HTTP/2 的推廣,它的應(yīng)用越來(lái)越廣泛。
十五、瀏覽器存儲(chǔ)
(1)Cookie
Cookie 本質(zhì)上就是瀏覽器里面存儲(chǔ)的一個(gè)很小的文本文件,內(nèi)部以鍵值對(duì)的方式來(lái)存儲(chǔ)。向同一個(gè)域名下發(fā)送請(qǐng)求,都會(huì)攜帶相同的 Cookie,服務(wù)器拿到 Cookie 進(jìn)行解析,便能拿到客戶(hù)端的狀態(tài)。
Cookie就是用來(lái)做狀態(tài)存儲(chǔ)的。缺陷如下:
- 容量缺陷。Cookie 的體積上限只有4KB。
- 性能缺陷。Cookie 緊跟域名,不管域名下面的某一個(gè)地址需不需要這個(gè) Cookie ,請(qǐng)求都會(huì)攜帶上完整的 Cookie,這樣隨著請(qǐng)求數(shù)的增多,其實(shí)會(huì)造成巨大的性能浪費(fèi)的,因?yàn)檎?qǐng)求攜帶了很多不必要的內(nèi)容。
- 安全缺陷。由于 Cookie 以純文本的形式在瀏覽器和服務(wù)器中傳遞。在HttpOnly為 false 的情況下,Cookie 信息能直接通過(guò) JS 腳本來(lái)讀取。
(2)localStorage
也是針對(duì)一個(gè)域名,即在同一個(gè)域名下,會(huì)存儲(chǔ)相同的一段localStorage。與Cookie的區(qū)別如下:
- 容量。localStorage 的容量上限為5M。對(duì)于一個(gè)域名是持久存儲(chǔ)的。
- 只存在客戶(hù)端,默認(rèn)不參與與服務(wù)端的通信。這樣就很好地避免了 Cookie 帶來(lái)的性能問(wèn)題和安全問(wèn)題。
- 接口封裝。通過(guò)localStorage暴露在全局,并通過(guò)它的 setItem 和 getItem等方法進(jìn)行操作。
(3)sessionStorage
- 容量。容量上限也為 5M。
- 只存在客戶(hù)端,默認(rèn)不參與與服務(wù)端的通信。
- 接口封裝。
但sessionStorage和localStorage有一個(gè)本質(zhì)的區(qū)別,那就是前者只是會(huì)話(huà)級(jí)別的存儲(chǔ),并不是持久化存儲(chǔ)。會(huì)話(huà)結(jié)束,也就是頁(yè)面關(guān)閉,這部分sessionStorage就不復(fù)存在了。
(4)IndexedDB
IndexedDB是運(yùn)行在瀏覽器中的非關(guān)系型數(shù)據(jù)庫(kù), 本質(zhì)上是數(shù)據(jù)庫(kù),理論上這個(gè)容量是沒(méi)有上限的。支持事務(wù)和二進(jìn)制存儲(chǔ)。
- 鍵值對(duì)存儲(chǔ),內(nèi)部采用對(duì)象倉(cāng)庫(kù)存儲(chǔ)方式。
- 異步操作,數(shù)據(jù)庫(kù)的讀寫(xiě)屬于IO操作,瀏覽器提供了異步IO支持。
- 受到同源策略限制,無(wú)法跨域訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)。
總結(jié)
以上是生活随笔為你收集整理的大话Chrome浏览器原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 全民写作一夜成名 Web2.0:作家造富
- 下一篇: chrome浏览器完整保存整个网页 可离