肝完《瀏覽器工作原理與實(shí)踐》,我總結(jié)了這些
作者:wuwhs
簡(jiǎn)介:深圳市某科技公司 前端工程師
來(lái)源:SegmentFault 思否社區(qū)
前言
作為一名前端er,日常工作打交道最多(之一)的莫過(guò)于熟悉而又陌生的瀏覽器了,熟悉是每天都會(huì)基于瀏覽器的應(yīng)用層面之上碼業(yè)務(wù),陌生是很多人可能跟我一樣不熟悉其內(nèi)部運(yùn)行原理。
js是怎樣運(yùn)行的呢?
精美樣式頁(yè)面是怎樣渲染到電腦屏幕的呢?
在開(kāi)放的互聯(lián)網(wǎng)它又是怎樣保證我們個(gè)人信息安全的呢?
帶著種種疑云開(kāi)始肝李兵老師的《瀏覽器基本原理與實(shí)踐》,不得不說(shuō),大家之作,通俗易懂,層層撥開(kāi)云霧見(jiàn)青天,下面就(非常非常)簡(jiǎn)單總結(jié)一下。
Chrome 架構(gòu):僅僅打開(kāi)了 1 個(gè)頁(yè)面,為什么有 4 個(gè)進(jìn)程
線程和進(jìn)程區(qū)別:多線程可以并行處理任務(wù),線程不能單獨(dú)存在,它是由進(jìn)程來(lái)啟動(dòng)和管理的。一個(gè)進(jìn)程是一個(gè)程序的運(yùn)行實(shí)例。
線程和進(jìn)程的關(guān)系:
1、進(jìn)程中任意一線程執(zhí)行出錯(cuò),都會(huì)導(dǎo)致整個(gè)進(jìn)程的崩潰。
2、線程之間共享進(jìn)程中的數(shù)據(jù)。
3、當(dāng)一個(gè)進(jìn)程關(guān)閉后,操作系統(tǒng)會(huì)回收進(jìn)程所占用的內(nèi)存。
4、進(jìn)程之間的內(nèi)容相互隔離。
單進(jìn)程瀏覽器:
1、不穩(wěn)定。單進(jìn)程中的插件、渲染線程崩潰導(dǎo)致整個(gè)瀏覽器崩潰。
2、不流暢。腳本(死循環(huán))或插件會(huì)使瀏覽器卡頓。
3、不安全。插件和腳本可以獲取到操作系統(tǒng)任意資源。
多進(jìn)程瀏覽器:
1、解決不穩(wěn)定。進(jìn)程相互隔離,一個(gè)頁(yè)面或者插件崩潰時(shí),影響僅僅時(shí)當(dāng)前插件或者頁(yè)面,不會(huì)影響到其他頁(yè)面。
2、解決不流暢。腳本阻塞當(dāng)前頁(yè)面渲染進(jìn)程,不會(huì)影響到其他頁(yè)面。
3、解決不安全。采用多進(jìn)程架構(gòu)使用沙箱。沙箱看成時(shí)操作系統(tǒng)給進(jìn)程上來(lái)一把鎖,沙箱的程序可以運(yùn)行,但是不能在硬盤(pán)上寫(xiě)入任何數(shù)據(jù),也不能在敏感位置讀取任何數(shù)據(jù)。
多進(jìn)程架構(gòu):分為 瀏覽器進(jìn)程、渲染進(jìn)程、GPU 進(jìn)程、網(wǎng)絡(luò)進(jìn)程、插件進(jìn)程。
缺點(diǎn):
1、資源占用高。
2、體系架構(gòu)復(fù)雜。
面向服務(wù)架構(gòu):把原來(lái)的各種模塊重構(gòu)成獨(dú)立的服務(wù),每個(gè)服務(wù)都可以在獨(dú)立的進(jìn)程中運(yùn)行,訪問(wèn)服務(wù)必須使用定義好的接口,通過(guò) IPC 通訊,使得系統(tǒng)更內(nèi)聚、松耦合、易維護(hù)和拓展。
TCP 協(xié)議:如何保證頁(yè)面文件能被完整送達(dá)瀏覽器
IP 頭是 IP 數(shù)據(jù)包開(kāi)頭的信息,包含 IP 版本、源 IP 地址、目標(biāo) IP 地址、生存時(shí)間等信息;
UDP 頭中除了目的端口,還有源端口號(hào)等信息;
IP 負(fù)責(zé)把數(shù)據(jù)包送達(dá)目的主機(jī);
UDP 負(fù)責(zé)把數(shù)據(jù)包送達(dá)具體應(yīng)用;
對(duì)于錯(cuò)誤的數(shù)據(jù)包,UDP 不提供重發(fā)機(jī)制,只是丟棄當(dāng)前的包,不能保證數(shù)據(jù)的可靠性,但是傳輸速度非常塊;
TCP 頭除了包含了目標(biāo)端口和本機(jī)端口號(hào)外,還提供了用于排序的序列號(hào),保證了數(shù)據(jù)完整地傳輸,它的連接可分為三個(gè)階段:建立連接、傳輸數(shù)據(jù)和斷開(kāi)連接;
HTTP 請(qǐng)求流程:為什么很多站點(diǎn)第二次打開(kāi)速度會(huì)很快
瀏覽器中的 HTTP 請(qǐng)求從發(fā)起到結(jié)束一共經(jīng)歷如下八個(gè)階段:構(gòu)建請(qǐng)求、查找緩存、準(zhǔn)備 IP 和端口、等待 TCP 隊(duì)列、建立 TCP 連接、發(fā)起 HTTP 請(qǐng)求、服務(wù)器處理請(qǐng)求、服務(wù)器返回請(qǐng)求和斷開(kāi)連接;
構(gòu)建請(qǐng)求。瀏覽器構(gòu)建請(qǐng)求行,構(gòu)建好后,準(zhǔn)備發(fā)起網(wǎng)絡(luò)請(qǐng)求;
查找緩存。在真正發(fā)起請(qǐng)求前瀏覽器會(huì)查詢(xún)緩存中是否有請(qǐng)求資源副本,有則攔截請(qǐng)求,返回資源副本,否則進(jìn)入網(wǎng)絡(luò)請(qǐng)求;
準(zhǔn)備 IP 地址和端口。HTTP 網(wǎng)絡(luò)請(qǐng)求需要和服務(wù)器建立 TCP 連接,而建立 TCP 連接需要準(zhǔn)備 IP 地址和端口號(hào),瀏覽器需要請(qǐng)求 DNS 返回域名對(duì)應(yīng)的 IP,同時(shí)會(huì)緩存域名解析結(jié)果,供下次查詢(xún)使用;
等待 TCP 隊(duì)列。Chrome 機(jī)制,同一個(gè)域名同時(shí)最多只能建立 6 個(gè) TCP 連接;
建立 TCP 連接。TCP 通過(guò)“三次握手”建立連接,傳輸數(shù)據(jù),“四次揮手”斷開(kāi)連接;
發(fā)送 HTTP 請(qǐng)求。建立 TCP 連接后,瀏覽器就可以和服務(wù)器進(jìn)行 HTTP 數(shù)據(jù)傳輸了,首先會(huì)向服務(wù)器發(fā)送請(qǐng)求行,然后以請(qǐng)求頭形式發(fā)送一些其他信息,如果是 POST 請(qǐng)求還會(huì)發(fā)送請(qǐng)求體;
服務(wù)器處理請(qǐng)求。首先服務(wù)器會(huì)返回響應(yīng)行,隨后,服務(wù)器向?yàn)g覽器發(fā)送響應(yīng)頭和響應(yīng)體。通常服務(wù)器返回?cái)?shù)據(jù),就要關(guān)閉 TCP 連接,如果請(qǐng)求頭或者響應(yīng)頭有 Connection:keep-alive TCP 保持打開(kāi)狀態(tài);
導(dǎo)航流程:從輸入 URL 到頁(yè)面展示這中間發(fā)生了什么
用戶(hù)輸入 URL 并回車(chē);
瀏覽器進(jìn)程檢查 URL,組裝協(xié)議,構(gòu)成完整 URL;
瀏覽器進(jìn)程通過(guò)進(jìn)程通信(IPC)把 URL 請(qǐng)求發(fā)送給網(wǎng)絡(luò)進(jìn)程;
網(wǎng)絡(luò)進(jìn)程接收到 URL 請(qǐng)求后檢查本地緩存是否緩存了該請(qǐng)求資源,如果有則將該資源返回給瀏覽器進(jìn)程;
如果沒(méi)有,網(wǎng)絡(luò)進(jìn)程向 web 服務(wù)器發(fā)起 http 請(qǐng)求(網(wǎng)絡(luò)請(qǐng)求),請(qǐng)求流程如下:
進(jìn)行 DNS 解析,獲取服務(wù)器 IP 地址,端口
利用 IP 地址和服務(wù)器建立 tcp 連接構(gòu)建請(qǐng)求頭信息
發(fā)送請(qǐng)求頭信息
服務(wù)器響應(yīng)后,網(wǎng)絡(luò)進(jìn)程接收響應(yīng)頭和響應(yīng)信息,并解析響應(yīng)內(nèi)容
網(wǎng)絡(luò)進(jìn)程解析響應(yīng)流程:
檢查狀態(tài)碼,如果是 301/302,則需要重定向,從 Location 自動(dòng)讀取地址,重新進(jìn)行第 4 步,如果是 200,則繼續(xù)處理請(qǐng)求
200 響應(yīng)處理:檢查響應(yīng)類(lèi)型 Content-Type,如果是字節(jié)流類(lèi)型,則將該請(qǐng)求提交給下載管理器,該導(dǎo)航流程結(jié)束,不再進(jìn)行后續(xù)渲染。如果是 html 則通知瀏覽器進(jìn)程準(zhǔn)備渲染進(jìn)程進(jìn)行渲染
準(zhǔn)備渲染進(jìn)程:
瀏覽器進(jìn)程檢查當(dāng)前 URL 是否和之前打開(kāi)的渲染進(jìn)程根域名是否相同,如果相同,則復(fù)用原來(lái)的進(jìn)程,如果不同,則開(kāi)啟新的渲染進(jìn)程
傳輸數(shù)據(jù)、更新?tīng)顟B(tài):
渲染進(jìn)程準(zhǔn)備好后,瀏覽器向渲染進(jìn)程發(fā)起“提交文檔”的消息,渲染進(jìn)程接收到消息和網(wǎng)絡(luò)進(jìn)程建立傳輸數(shù)據(jù)的“管道”
渲染進(jìn)程接收完數(shù)據(jù)后,向?yàn)g覽器發(fā)送“確認(rèn)提交”
瀏覽器進(jìn)程接收到確認(rèn)消息后 engine 瀏覽器界面狀態(tài):安全、地址 URL、前進(jìn)后退的歷史狀態(tài)、更新 web 頁(yè)面
渲染流程(上):HTML、CSS 和 JavaScript 是如何變成頁(yè)面的
瀏覽器不能直接理解 HTML 數(shù)據(jù),需要將其轉(zhuǎn)化為 DOM 樹(shù)結(jié)構(gòu);
生成 DOM 樹(shù)后,根據(jù) CSS 樣式表,計(jì)算出 DOM 樹(shù)所有節(jié)點(diǎn)樣式;
創(chuàng)建布局樹(shù):遍歷 DOM 樹(shù)所有可見(jiàn)節(jié)點(diǎn),把這些節(jié)點(diǎn)加到布局中,不可見(jiàn)節(jié)點(diǎn)忽略,如 head 標(biāo)簽下所有內(nèi)容,display: none 元素;
渲染流程(下):HTML、CSS 和 JavaScript 是如何變成頁(yè)面的
分層:層疊上下文屬性的元素(比如定位屬性元素、透明屬性元素、CSS 濾鏡屬性元素)提升為單獨(dú)的一層,需要裁剪的地方(比如出現(xiàn)滾動(dòng)條)也會(huì)被創(chuàng)建為圖層;
圖層繪制:完成圖層樹(shù)構(gòu)建后,渲染引擎會(huì)對(duì)圖層樹(shù)每一層進(jìn)行繪制,把一個(gè)圖層拆分成小的繪制指令,再把指令按照順序組成一個(gè)帶繪制列表;
有些情況圖層很大,一次繪制所有圖層內(nèi)容,開(kāi)銷(xiāo)太大,合成線程會(huì)將圖層劃分為圖塊(256x256 或者 512x512);
合成線程將圖塊提交給柵格線程進(jìn)行柵格化,將圖塊轉(zhuǎn)換為位圖。柵格化過(guò)程都會(huì)使用 GPU 加速,生成的位圖保存周期 GPU 內(nèi)存中;
一旦所有圖塊都被柵格化,合成線程會(huì)生成一個(gè)繪制圖塊命令(DrawQuad),然會(huì)將命令提交給瀏覽器進(jìn)程,viz 組件接收到該指令,將頁(yè)面內(nèi)容繪制到內(nèi)存中,顯示在屏幕上;
重排:通過(guò) JavaScript 或者 CSS 修改元素幾何位置屬性,會(huì)觸發(fā)重新布局,解析后面一系列子階段;重繪:不引起布局變換,直接進(jìn)入繪制及其以后子階段;合成:跳過(guò)布局和繪制階段,執(zhí)行的后續(xù)操作,發(fā)生在合成線程,非主線程;
變量提升:javascript 代碼是按順序執(zhí)行的嗎
JavaScript 代碼在執(zhí)行之前需要先編譯,在編譯階段,變量和函數(shù)會(huì)被存放到變量環(huán)境中,變量默認(rèn)值會(huì)被設(shè)置為 undefined;
在代碼執(zhí)行階段,JavaScript 引擎會(huì)從變量環(huán)境中查找自定義的變量和函數(shù);
如果在編譯階段,竄愛(ài)兩個(gè)相同的函數(shù),那么最終放在變量環(huán)境中的是最后定義的那個(gè),后定義的覆蓋先定義的;
調(diào)用棧:為什么 JavaScript 代碼會(huì)出現(xiàn)棧溢出
每調(diào)用一個(gè)函數(shù),JavaScript 引擎會(huì)為其創(chuàng)建執(zhí)行上下文壓入調(diào)用棧,然后,JavaScript 引擎開(kāi)始執(zhí)行函數(shù)代碼;
如果一個(gè)函數(shù) A 調(diào)用另外一個(gè)函數(shù) B,那么 JavaScript 引擎會(huì)為 B 函數(shù)創(chuàng)建執(zhí)行上下文,并將 B 函數(shù)的執(zhí)行上下文壓入棧頂;
當(dāng)前函數(shù)執(zhí)行完畢后,JavaScript 引擎會(huì)將該函數(shù)的執(zhí)行上下文彈出棧;
當(dāng)分配的調(diào)用棧空間被占滿(mǎn)時(shí),會(huì)引發(fā)“堆棧溢出”問(wèn)題;
塊級(jí)作用域:var 缺陷以及為什么要引入 let 和 const
let、const 申明的變量不會(huì)被提升。在 javascript 引擎編譯后,會(huì)保存在詞法環(huán)境中;
塊級(jí)作用域在代碼執(zhí)行時(shí),將 let、const 變量存放在詞法環(huán)境的一個(gè)單獨(dú)的區(qū)域。詞法環(huán)境內(nèi)部維護(hù)一個(gè)小型的棧結(jié)構(gòu),作用域內(nèi)部變量壓入棧頂。作用域執(zhí)行完,從棧頂彈出;
作用域鏈和閉包:代碼中出現(xiàn)相同的變量,JavaScript 引擎如何選擇
使用一個(gè)變量,JavaScript 引擎會(huì)在當(dāng)前的執(zhí)行上下文中查找變量,如果沒(méi)有找到,會(huì)繼續(xù)在 outer(執(zhí)行環(huán)境指向外部執(zhí)行上下文的引用)所指向的執(zhí)行上下文中查找;
JavaScript 執(zhí)行過(guò)程,作用域鏈?zhǔn)怯稍~法作用域決定,而詞法作用域是由代碼中函數(shù)聲明的位置決定;
根據(jù)詞法作用域的規(guī)則,內(nèi)部函數(shù)總是可以訪問(wèn)其外部函數(shù)中聲明的變量,當(dāng)通過(guò)調(diào)用一個(gè)外部函數(shù)返回一個(gè)內(nèi)部函數(shù)后,即使外部函數(shù)已經(jīng)執(zhí)行結(jié)束了,但是內(nèi)部函數(shù)引用外部函數(shù)的變量依舊保存在內(nèi)存中,把這些變量的集合稱(chēng)為閉包;
this:從 JavaScript 執(zhí)行上下文視角講 this
首先創(chuàng)建一個(gè)控對(duì)象 tempObj;
接著調(diào)用 CreateObj.call 方法,并將 tempObj 作為 call 方法的參數(shù),這樣當(dāng) createObj 的執(zhí)行上下文創(chuàng)建時(shí),它的 this 就指向 tempObj 對(duì)象;
然后執(zhí)行 CreateObj 函數(shù),此時(shí)的 CreateObj 函數(shù)執(zhí)行上下文中的 this 指向 tempObj 對(duì)象;
最后返回 tempObj 對(duì)象。
當(dāng)函數(shù)最為對(duì)象的方法調(diào)用時(shí),函數(shù)中的 this 就是該對(duì)象;
當(dāng)函數(shù)被正常調(diào)用時(shí),在嚴(yán)格模式下,this 值是 undefined,非嚴(yán)格模式下 this 指向的是全局對(duì)象 window;
嵌套函數(shù)中的 this 不會(huì)繼承外層函數(shù)的 this 值;
箭頭函數(shù)沒(méi)有自己的執(zhí)行上下文,this 是外層函數(shù)的 this。
棧空間和堆空間:數(shù)據(jù)是如何存儲(chǔ)的
弱類(lèi)型語(yǔ)言:支持隱式轉(zhuǎn)換的語(yǔ)言。
原始類(lèi)型數(shù)據(jù)存放在棧中,引用類(lèi)型數(shù)據(jù)存放在堆中。堆中的數(shù)據(jù)是通過(guò)引用與變量關(guān)系聯(lián)系起來(lái)的。
垃圾回收:垃圾數(shù)據(jù)如何自動(dòng)回收
棧中數(shù)據(jù)回收:執(zhí)行狀態(tài)指針 ESP 在執(zhí)行棧中移動(dòng),移過(guò)某執(zhí)行上下文,就會(huì)被銷(xiāo)毀;
堆中數(shù)據(jù)回收:V8 引擎采用標(biāo)記-清除算法;
V8 把堆分為兩個(gè)區(qū)域——新生代和老生代,分別使用副、主垃圾回收器;
副垃圾回收器負(fù)責(zé)新生代垃圾回收,小對(duì)象(1 ~ 8M)會(huì)被分配到該區(qū)域處理;
新生代采用 scavenge 算法處理:將新生代空間分為兩半,一半空閑,一半存對(duì)象,對(duì)對(duì)象區(qū)域做標(biāo)記,存活對(duì)象復(fù)制排列到空閑區(qū)域,沒(méi)有內(nèi)存碎片,完成后,清理對(duì)象區(qū)域,角色反轉(zhuǎn);
新生代區(qū)域兩次垃圾回收還存活的對(duì)象晉升至老生代區(qū)域;
主垃圾回收器負(fù)責(zé)老生區(qū)垃圾回收,大對(duì)象,存活時(shí)間長(zhǎng);
新生代區(qū)域采用標(biāo)記-清除算法回收垃圾:從根元素開(kāi)始,遞歸,可到達(dá)的元素活動(dòng)元素,否則是垃圾數(shù)據(jù);
為了不造成卡頓,標(biāo)記過(guò)程被切分為一個(gè)個(gè)子標(biāo)記,交替進(jìn)行。
編譯器和解析器:V8 如何執(zhí)行一段 JavaScript 代碼的
計(jì)算機(jī)語(yǔ)言可以分為兩種:編譯型和解釋型語(yǔ)言。編譯型語(yǔ)言經(jīng)過(guò)編譯器編譯后保留機(jī)器能讀懂的二進(jìn)制文件,比如 C/C++,go 語(yǔ)言。解釋型語(yǔ)言是在程序運(yùn)行時(shí)通過(guò)解釋器對(duì)程序進(jìn)行動(dòng)態(tài)解釋和執(zhí)行,比如 Python,JavaScript 語(yǔ)言。
編譯型語(yǔ)言的編譯過(guò)程:編譯器首先將代碼進(jìn)行詞法分析、語(yǔ)法分析,生成抽象語(yǔ)法樹(shù)(AST),然后優(yōu)化代碼,最后生成處理器能夠理解的機(jī)器碼;
解釋型語(yǔ)言解釋過(guò)程:解釋器會(huì)對(duì)代碼進(jìn)行詞法分析、語(yǔ)法分析,并生產(chǎn)抽象語(yǔ)法樹(shù)(AST),不過(guò)它會(huì)再基于抽象語(yǔ)法樹(shù)生成字節(jié)碼,最后根據(jù)字節(jié)碼執(zhí)行程序;
AST 的生成:第一階段是分詞(詞法分析),將一行行源碼拆解成一個(gè)個(gè) token(語(yǔ)法上不可再分、最小單個(gè)字符)。第二階段是解析(語(yǔ)法分析),將上一步生成的 token 數(shù)據(jù),根據(jù)語(yǔ)法規(guī)則轉(zhuǎn)為 AST,這一階段會(huì)檢查語(yǔ)法錯(cuò)誤;
字節(jié)碼存在的意義:直接將 AST 轉(zhuǎn)化為機(jī)器碼,執(zhí)行效率是非常高,但是消耗大量?jī)?nèi)存,從而先轉(zhuǎn)化為字節(jié)碼解決內(nèi)存問(wèn)題;
解釋器 ignition 在解釋執(zhí)行字節(jié)碼,同時(shí)會(huì)手機(jī)代碼信息,發(fā)現(xiàn)某一部分代碼是熱點(diǎn)代碼(HotSpot),編譯器把熱點(diǎn)的字節(jié)碼轉(zhuǎn)化為機(jī)器碼,并保存起來(lái),下次使用;
字節(jié)碼配合解釋器和編譯器的計(jì)數(shù)實(shí)現(xiàn)稱(chēng)為即時(shí)編譯(JIT)。
消息隊(duì)列和事件循環(huán):頁(yè)面是怎么活起來(lái)的
每個(gè)渲染進(jìn)程都有一個(gè)主線程,主線程會(huì)處理 DOM,計(jì)算樣式,處理布局,JavaScript 任務(wù)以及各種輸入事件;
維護(hù)一個(gè)消息隊(duì)列,新任務(wù)(比如 IO 線程)添加到消息隊(duì)列尾部,主線程循環(huán)地從消息隊(duì)列頭部讀取任務(wù),執(zhí)行任務(wù);
解決處理優(yōu)先級(jí)高的任務(wù):消息隊(duì)列的中的任務(wù)稱(chēng)為宏任務(wù),每個(gè)宏任務(wù)中都會(huì)包含一個(gè)微任務(wù)隊(duì)列,在執(zhí)行宏任務(wù)的過(guò)程中,如果 DOM 有變化,將該變化添加到微任務(wù)隊(duì)列中;
解決單個(gè)任務(wù)執(zhí)行時(shí)長(zhǎng)過(guò)久:JavaScript 通過(guò)回調(diào)功能來(lái)規(guī)避。
webapi:setTimeout 是怎么實(shí)現(xiàn)的
JavaScript 調(diào)用 setTimeout 設(shè)置回調(diào)函數(shù)的時(shí)候,渲染進(jìn)程會(huì)創(chuàng)建一個(gè)回調(diào)任務(wù),延時(shí)執(zhí)行隊(duì)列存放定時(shí)器任務(wù);
當(dāng)定時(shí)器任務(wù)到期,就會(huì)從延時(shí)隊(duì)列中取出并執(zhí)行;
如果當(dāng)前任務(wù)執(zhí)行時(shí)間過(guò)久,會(huì)影響延時(shí)到期定時(shí)器任務(wù)的執(zhí)行;
如果 setTimeout 存在嵌套調(diào)用(5 次以上),判斷該函數(shù)方法被阻塞,那么系統(tǒng)會(huì)設(shè)置最短時(shí)間間隔為 4 秒;
未激活的頁(yè)面,setTimeout 執(zhí)行最小間隔是 1000 毫秒,目的是為了降低加載損耗;
延時(shí)執(zhí)行時(shí)間最大值是 24.8 天,因?yàn)檠訒r(shí)值是以 32 個(gè) bit 存儲(chǔ)的;
setTimeout 設(shè)置的回調(diào)函數(shù)中的 this 指向全局 window。
webpai:XMLHttpRequest 是怎么實(shí)現(xiàn)的
XMLHttpRequest onreadystatechange 處理流程:未初始化 -> OPENED -> HEADERS_RECEIVED -> LOADING -> DONE;
渲染進(jìn)程會(huì)將請(qǐng)求發(fā)送給網(wǎng)絡(luò)進(jìn)程,然后網(wǎng)絡(luò)進(jìn)程負(fù)責(zé)資源下載,等網(wǎng)絡(luò)進(jìn)程接收到數(shù)據(jù)后,利用 IPC 通知渲染進(jìn)程;
渲染進(jìn)程接收到消息之后,會(huì)將 xhr 回調(diào)函數(shù)封裝成任務(wù)并添加到消息隊(duì)列中,等主線程循環(huán)系統(tǒng)執(zhí)行到該任務(wù)的時(shí)候,會(huì)根據(jù)相關(guān)狀態(tài)來(lái)調(diào)用回調(diào)函數(shù)。
宏任務(wù)和微任務(wù):不是所有的任務(wù)都是一個(gè)待遇
消息隊(duì)列中的任務(wù)為宏任務(wù)。渲染進(jìn)程內(nèi)部會(huì)維護(hù)多個(gè)消息隊(duì)列,比如延時(shí)執(zhí)行隊(duì)列和普通消息隊(duì)列,主線程采用 for 循環(huán),不斷地從這些任務(wù)隊(duì)列中取出任務(wù)并執(zhí)行;
微任務(wù)是一個(gè)需要異步執(zhí)行的函數(shù),執(zhí)行時(shí)機(jī)是在主函數(shù)執(zhí)行結(jié)束之后、當(dāng)前宏任務(wù)結(jié)束之前;
V8 在執(zhí)行 javascript 腳本時(shí),會(huì)為其創(chuàng)建一個(gè)全局執(zhí)行上下文,同時(shí)會(huì)創(chuàng)建一個(gè)微任務(wù)隊(duì)列;
執(zhí)行微任務(wù)過(guò)程中產(chǎn)生的微任務(wù)不會(huì)推遲到下個(gè)宏任務(wù)中執(zhí)行,而是在當(dāng)前宏任務(wù)中繼續(xù)執(zhí)行;
使用 Promise 告別回調(diào)函數(shù)
使用 Promise 解決了回調(diào)地獄問(wèn)題,消滅嵌套和多次處理;
模擬實(shí)現(xiàn) Promise
function Bromise(executor) {
var _onResolve = null
this.then = function (onResolve) {
_onResolve = onResolve
}
function resolve(value) {
setTimeout(() => {
_onResolve(value)
}, 0)
}
executor(resolve, null)
}
async await 使用同步方式寫(xiě)異步代碼
生成器函數(shù)是一個(gè)帶星號(hào)函數(shù),而且是可以暫停執(zhí)行和回復(fù)執(zhí)行的;
生成器函數(shù)內(nèi)部執(zhí)行一段代碼,遇到 yield 關(guān)鍵字,javascript 引擎返回關(guān)鍵字后面的內(nèi)容給外部,并且暫停該函數(shù)的執(zhí)行;
外部函數(shù)可以同步 next 方法恢復(fù)函數(shù)的執(zhí)行;
協(xié)程是一種比線程更加輕量級(jí)的存在,協(xié)程可以看成是跑在線程上的任務(wù),一個(gè)線程可以存在多個(gè)協(xié)程,但是同時(shí)只能執(zhí)行一個(gè)協(xié)程,如果 A 協(xié)程啟動(dòng) B 協(xié)程,A 為 B 的父協(xié)程;
協(xié)程不被操作協(xié)同內(nèi)核所管理,而完全由程序所控制,這樣性能提升;
await xxx 會(huì)創(chuàng)建一個(gè) Promise 對(duì)象,將 xxx 任務(wù)提交給微任務(wù)隊(duì)列;
暫停當(dāng)前協(xié)程的執(zhí)行,將主線程的控制權(quán)力轉(zhuǎn)交給父協(xié)程執(zhí)行,同時(shí)將 Promise 對(duì)象返回給父協(xié)程,繼續(xù)執(zhí)行父協(xié)程;
父協(xié)程執(zhí)行結(jié)束之前會(huì)檢查微任務(wù)隊(duì)列,微任務(wù)隊(duì)列中有 resolve(xxx) 等待執(zhí)行,觸發(fā) then 的回調(diào)函數(shù);
回調(diào)函數(shù)被激活后,會(huì)將主線程的控制權(quán)交給協(xié)程,繼續(xù)執(zhí)行后續(xù)語(yǔ)句,完成后將控制權(quán)還給父協(xié)程。
頁(yè)面性能分析:利用 chrome 做 web 性能分析
Chrome 開(kāi)發(fā)者工具(簡(jiǎn)稱(chēng) DevTools)是一組網(wǎng)頁(yè)制作和調(diào)試的工具,內(nèi)嵌于 Google Chrome 瀏覽器中。它一共包含了 10 個(gè)功能面板,包括了 Elements、Console、Sources、NetWork、Performance、Memory、Application、Security、Audits 和 Layers。
DOM 樹(shù):JavaScript 是如何影響 DOM 樹(shù)構(gòu)建的
HTML 解析器(HTMLParse)負(fù)責(zé)將 HTML 字節(jié)流轉(zhuǎn)換為 DOM 結(jié)構(gòu);
HTML 解析器并不是等整個(gè)文檔加載完成之后再解析,而是網(wǎng)絡(luò)進(jìn)程加載流多少數(shù)據(jù),便解析多少數(shù)據(jù);
字節(jié)流轉(zhuǎn)換成 DOM 三個(gè)階段:1、字節(jié)流轉(zhuǎn)換為 Token;2、維護(hù)一個(gè) Token 棧,遇到 StartTag Token 入棧,遇到 EndTag Token 出棧;3、為每個(gè) Token 創(chuàng)建一個(gè) DOM 節(jié)點(diǎn);
JavaScript 文件和 CSS 樣式表文件都會(huì)阻塞 DOM 解析;
渲染流水線:CSS 如何影響首次加載時(shí)的白屏?xí)r間?
DOM 構(gòu)建結(jié)束之后,css 文件還未下載完成,渲染流水線空閑,因?yàn)橄乱徊绞呛铣刹季謽?shù),合成布局樹(shù)需要 CSSOM 和 DOM,這里需要等待 CSS 加載結(jié)束并解析成 CSSOM;
CSSOM 兩個(gè)作用:提供給 JavaScript 操作樣式表能力,為布局樹(shù)的合成提供基礎(chǔ)樣式信息;
在執(zhí)行 JavaScript 腳本之前,如果頁(yè)面中包含了外部 CSS 文件的引用,或者通過(guò) style 標(biāo)簽內(nèi)置了 CSS 內(nèi)容,那么渲染引擎還需要將這些內(nèi)容轉(zhuǎn)化為 CSSOM,因?yàn)?JavaScript 有修改 CSSOM 的能力,所以在執(zhí)行 JavaScript 之前,還需要依賴(lài) CSSOM。也就是說(shuō) CSS 在部分情況下也會(huì)阻塞 DOM 的生成。
分層和合成機(jī)制:為什么 CSS 動(dòng)畫(huà)比 JavaScript 高效
顯示器固定刷新頻率是 60HZ,即每秒更新 60 張圖片,圖片來(lái)自顯卡的前緩沖區(qū);
顯卡的職責(zé)是合成新的圖像,保存在后緩沖區(qū),然后后緩沖區(qū)和前緩沖區(qū)互換,顯卡更新頻率和顯示前刷新頻率不一致,就會(huì)造成視覺(jué)上的卡頓;
渲染流水線生成的每一副圖片稱(chēng)為一幀,生成一幀的方式有重排、重繪和合成三種;
重排會(huì)根據(jù) CSSOM 和 DOM 計(jì)算布局樹(shù),重繪沒(méi)有重新布局階段;
生成布局樹(shù)之后,渲染引擎根據(jù)布局樹(shù)特點(diǎn)轉(zhuǎn)化為層樹(shù),每一層解析出繪制列表;
柵格線程根據(jù)繪制列表中的指令生成圖片,每一層對(duì)應(yīng)一張圖片,合成線程將這些圖片合成一張圖片,發(fā)送到后緩存區(qū);
合成線程會(huì)將每個(gè)圖層分割成大小固定的圖塊,優(yōu)先繪制靠近視口的圖塊;
頁(yè)面性能:如何系統(tǒng)優(yōu)化頁(yè)面
加載階段:減少關(guān)鍵資源個(gè)數(shù),降低關(guān)鍵資源大小,降低關(guān)鍵資源的 RTT 次數(shù);
交互階段:減少 JavaScript 腳本執(zhí)行時(shí)間,避免強(qiáng)制同步布局:操作 DOM 的同時(shí)獲取布局樣式會(huì)引發(fā),避免布局抖動(dòng):多次執(zhí)行強(qiáng)制布局和抖動(dòng),合理利用 CSS 合成動(dòng)畫(huà):標(biāo)記 will-change,避免頻繁的垃圾回收;
CSS 實(shí)現(xiàn)一些變形、漸變、動(dòng)畫(huà)等特效,這是由 CSS 觸發(fā)的,并且是在合成線程中執(zhí)行,這個(gè)過(guò)程稱(chēng)為合成,它不會(huì)觸發(fā)重排或者重繪;
虛擬 DOM:虛擬 DOM 和真實(shí) DOM 有何不同
當(dāng)有數(shù)據(jù)更新時(shí), React 會(huì)生產(chǎn)一個(gè)新的虛擬 DOM,然會(huì)拿新的虛擬 DOM 和之前的虛擬 DOM 進(jìn)行比較,這個(gè)過(guò)程找出變化的節(jié)點(diǎn),然后將變化的節(jié)點(diǎn)應(yīng)用到 DOM 上;
最開(kāi)始的時(shí)候,比較兩個(gè) DOM 的過(guò)程是在一個(gè)遞歸函數(shù)里執(zhí)行的,其核心算法是 reconciliation。通常情況,這個(gè)比較過(guò)程執(zhí)行很快,不過(guò)虛擬 DOM 比較復(fù)雜時(shí),執(zhí)行比較函數(shù)可能占據(jù)主線程比較久的時(shí)間,這樣會(huì)導(dǎo)致其他任務(wù)的等待,造成頁(yè)面卡頓。React 團(tuán)隊(duì)重寫(xiě)了 reconciliation 算法,稱(chēng)為 Fiber reconciler,之前老的算法稱(chēng)為 Stack reconciler;
PWA:解決 web 應(yīng)用哪些問(wèn)題
PWA(Progressive Web App),漸進(jìn)式 Web 應(yīng)用。一個(gè)漸進(jìn)式過(guò)渡方案,讓普通站點(diǎn)過(guò)渡到 Web 應(yīng)用,降低站點(diǎn)改造代價(jià),逐漸支持新技術(shù),而不是一步到位;
PWA 引入 ServiceWorker 來(lái)試著解決離線存儲(chǔ)和消息推送問(wèn)題,引入 mainfest.json 來(lái)解決一級(jí)入口問(wèn)題;
暗轉(zhuǎn)了 ServiceWorker 模塊之后,WebApp 請(qǐng)求資源時(shí),會(huì)先通過(guò) ServiceWorker,讓它判斷是返回 Serviceworker 緩存的資源還是重新去網(wǎng)絡(luò)請(qǐng)求資源,一切的控制權(quán)交給 ServiceWorker 來(lái)處理;
在目前的 Chrome 架構(gòu)中,Service Worker 是運(yùn)行在瀏覽器進(jìn)程中的,因?yàn)闉g覽器進(jìn)程生命周期是最長(zhǎng)的,所以在瀏覽器的生命周期內(nèi),能夠?yàn)樗械捻?yè)面提供服務(wù);
WebComponent:像搭積木一樣構(gòu)建 web 應(yīng)用
CSS 的全局屬性會(huì)阻礙組件化,DOM 也是阻礙組件化的一個(gè)因素,因?yàn)轫?yè)面中只有一個(gè) DOM,任何地方都可以直接讀取和修改 DOM;
WebComponent 提供了對(duì)局部試圖封裝能力,可以讓 DOM、CSSOM 和 JavaScript 運(yùn)行在局部環(huán)境中;
template 創(chuàng)建模版,查找模版內(nèi)容,創(chuàng)建影子 DOM,模版添加到影子 DOM 上;
影子 DOM 可以隔離全局 CSS 和 DOM,但是 JavaScript 是不會(huì)被隔離的;
HTTP1:HTTP1 性能優(yōu)化
HTTP/0.9 基于 TCP 協(xié)議,三次握手建立連接,發(fā)送一個(gè) GET 請(qǐng)求行(沒(méi)有請(qǐng)求頭和請(qǐng)求體),服務(wù)器接收請(qǐng)求之后,讀取對(duì)應(yīng) HTML 文件,數(shù)據(jù)以 ASCII 字符流返回,傳輸完成斷開(kāi)連接;
HTTP/1.0 增加請(qǐng)求頭和響應(yīng)頭來(lái)進(jìn)行協(xié)商,在發(fā)起請(qǐng)求時(shí)通過(guò)請(qǐng)求頭告訴服務(wù)器它期待返回什么類(lèi)型問(wèn)題、什么形式壓縮、什么語(yǔ)言以及文件編碼。引入來(lái)狀態(tài)嗎,Cache 機(jī)制等;
HTTP/1.1 改進(jìn)持久化連接,解決建立 TCP 連接、傳輸數(shù)據(jù)和斷開(kāi)連接帶來(lái)的大量開(kāi)銷(xiāo),支持在一個(gè) TCP 連接上可以傳輸多個(gè) HTTP 請(qǐng)求,目前瀏覽器對(duì)于一個(gè)域名同時(shí)允許建立 6 個(gè) TCP 持久連接;
HTTP/1.1 引入 Chunk transfer 支持動(dòng)態(tài)生成內(nèi)容:服務(wù)器將數(shù)據(jù)分割成若干任意大小的數(shù)據(jù)塊,每個(gè)數(shù)據(jù)塊發(fā)送時(shí)附上上個(gè)數(shù)據(jù)塊的長(zhǎng)度,最后使用一個(gè)零長(zhǎng)度的塊作為發(fā)送數(shù)據(jù)完成的標(biāo)志。在 HTTP/1.1 需要在響應(yīng)頭中設(shè)置完整的數(shù)據(jù)大小,如 Content-Length。
HTTP2:如何提升網(wǎng)絡(luò)速度
HTTP/1.1 主要問(wèn)題:TCP 慢啟動(dòng);同時(shí)開(kāi)啟多條 TCP 連接,會(huì)競(jìng)爭(zhēng)固定寬帶;對(duì)頭阻塞問(wèn)題;
HTTP/2 在一個(gè)域名下只使用一個(gè) TCP 長(zhǎng)連接和消除對(duì)頭阻塞問(wèn)題;
多路復(fù)用的實(shí)現(xiàn):HTTP/2 添加了二進(jìn)制分幀層,將發(fā)送或響應(yīng)數(shù)據(jù)經(jīng)過(guò)二進(jìn)制分幀處理,轉(zhuǎn)化為一個(gè)個(gè)帶有請(qǐng)求 ID 編號(hào)的幀,服務(wù)器或者瀏覽器接收到響應(yīng)幀后,根據(jù)相同 ID 幀合并為一條完整信息;
設(shè)置請(qǐng)求優(yōu)先級(jí):發(fā)送請(qǐng)求可以設(shè)置請(qǐng)求優(yōu)先級(jí),服務(wù)器可以?xún)?yōu)先處理;
服務(wù)器推送:請(qǐng)求一個(gè) HTML 頁(yè)面,服務(wù)器可以知道引用了哪些 JavaScript 和 CSS 文件,附帶一起發(fā)送給瀏覽器;
頭部壓縮:對(duì)請(qǐng)求頭和響應(yīng)頭進(jìn)行壓縮;
HTTP3:甩掉 TCP、TCL 包袱,構(gòu)建高效網(wǎng)絡(luò)
雖然 HTTP/2 解決了應(yīng)用層面的對(duì)頭阻塞問(wèn)題,不過(guò)和 HTTP/1.1 一樣,HTTP/2 依然是基于 TCP 協(xié)議,而 TCP 最初是為了單連接而設(shè)計(jì);
TCP 可以看成是計(jì)算機(jī)之間的一個(gè)虛擬管道,數(shù)據(jù)從一端發(fā)送到另一端會(huì)被拆分為一個(gè)個(gè)按照順序排列的數(shù)據(jù)包,如果在傳輸過(guò)程中,有一個(gè)數(shù)據(jù)因?yàn)榫W(wǎng)絡(luò)故障或者其他原因丟失,那么整個(gè)連接會(huì)處于暫停狀態(tài),只有等到該數(shù)據(jù)重新傳輸;
由于 TCP 協(xié)議僵化,也不可能使用新的協(xié)議,HTTP/3 選擇了一個(gè)折衷的方法,基于現(xiàn)有的 UDP 協(xié)議,實(shí)現(xiàn)類(lèi)似 TC 片多路復(fù)用,傳輸可靠等功能,稱(chēng)為 QULC 協(xié)議;
QULC 實(shí)現(xiàn)類(lèi)似 TCP 流量控制,傳輸可靠功能;集成 TLS 加密功能;實(shí)現(xiàn)多路復(fù)用功能;
同源策略:為什么 XMLHttpRequst 不能跨域請(qǐng)求
協(xié)議、域名和端口號(hào)相同的 URL 是同源的;
同源策略會(huì)隔離不同源的 DOM、頁(yè)面數(shù)據(jù)和網(wǎng)絡(luò)通信;
頁(yè)面可以引用第三方資源,不過(guò)暴露出諸如 XSS 問(wèn)題,引入內(nèi)容安全策略 CSP 限制;
默認(rèn) XMLHttpRequest 和 Fetch 不能跨站請(qǐng)求資源,引入跨域資源共享(CORS)進(jìn)行跨域訪問(wèn)控制;
跨站腳本攻擊 XSS:為什么 cookie 中有 httpOnly 屬性
XSS 跨站腳本,往 HTML 文件中注入惡意代碼,對(duì)用戶(hù)實(shí)施攻擊;
XSS 攻擊主要有存儲(chǔ)型 XSS 攻擊、反射型 XSS 攻擊和 DOM 的 XSS 攻擊;
阻止 XSS 攻擊:服務(wù)器對(duì)腳本進(jìn)行過(guò)濾或轉(zhuǎn)碼,利用 CSP 策略,使用 HttpOnly;
CSRF 攻擊:陌生連接不要隨便點(diǎn)
CSRF 跨站請(qǐng)求偽造,利用用戶(hù)的登錄狀態(tài),通過(guò)第三方站點(diǎn)攻擊;
避免 CSRF 攻擊:利用 SameSite(三種模式:Strict、Lax、None) 讓瀏覽器禁止第三方站點(diǎn)發(fā)起請(qǐng)求攜帶關(guān)鍵 Cookie;驗(yàn)證請(qǐng)求的來(lái)源站點(diǎn),請(qǐng)求頭中的 Referer 和 Origin 屬性;利用 CSRF Token;
沙盒:頁(yè)面和系統(tǒng)之間的隔離墻
瀏覽器被劃分為瀏覽器內(nèi)核和渲染內(nèi)核兩個(gè)核心模塊,其中瀏覽器內(nèi)核石油網(wǎng)絡(luò)進(jìn)程、瀏覽器主進(jìn)程和 GPU 進(jìn)程組成的,渲染內(nèi)核就是渲染進(jìn)程;
瀏覽器中的安全沙箱是利用操作系統(tǒng)提供的安全技術(shù),讓渲染進(jìn)程在執(zhí)行過(guò)程中無(wú)法訪問(wèn)或者修改操作系統(tǒng)中的數(shù)據(jù),在渲染進(jìn)程需要訪問(wèn)系統(tǒng)資源的時(shí)候,需要通過(guò)瀏覽器內(nèi)核來(lái)實(shí)現(xiàn),然后將訪問(wèn)的結(jié)果通過(guò) IPC 轉(zhuǎn)發(fā)給渲染進(jìn)程;
站點(diǎn)隔離(Site Isolation)將同一站點(diǎn)(包含相同根域名和相同協(xié)議的地址)中相互關(guān)聯(lián)的頁(yè)面放到同一個(gè)渲染進(jìn)程中執(zhí)行;
實(shí)現(xiàn)站點(diǎn)隔離,就可以將惡意的 iframe 隔離在惡意進(jìn)程內(nèi)部,使得它無(wú)法繼續(xù)訪問(wèn)其他 iframe 進(jìn)程的內(nèi)容,因此無(wú)法攻擊其他站點(diǎn);
HTTPS:讓數(shù)據(jù)傳輸更安全
在 TCP 和 HTTP 之間插入一個(gè)安全層,所有經(jīng)過(guò)安全層的數(shù)據(jù)都會(huì)被加密或者解密;
對(duì)稱(chēng)加密:瀏覽器發(fā)送加密套件列表和一個(gè)隨機(jī)數(shù) client-random,服務(wù)器會(huì)從加密套件中選取一個(gè)加密套件,然后生成一個(gè)隨機(jī)數(shù) service-random,返回給瀏覽器。這樣瀏覽器和服務(wù)器都有相同 client-random 和 service-random,再用相同的方法將兩者混合生成一個(gè)密鑰 master secret,雙方就可以進(jìn)行數(shù)據(jù)加密傳輸了;
對(duì)稱(chēng)加密缺點(diǎn):client-random 和 service-random 的過(guò)程都是明文,黑客可以拿到協(xié)商的加密套件和雙方隨機(jī)數(shù),生成密鑰,數(shù)據(jù)可以被破解;
非對(duì)稱(chēng)加密:瀏覽器發(fā)送加密套件列表給服務(wù)器,服務(wù)器選擇一個(gè)加密套件,返回加密套件和公鑰,瀏覽器用公鑰加密數(shù)據(jù),服務(wù)器用私鑰解密;
非對(duì)稱(chēng)加密缺點(diǎn):加密效率太低,不能保證服務(wù)器發(fā)送給瀏覽器的數(shù)據(jù)安全,黑客可以獲取公鑰;
對(duì)稱(chēng)加密結(jié)合非對(duì)稱(chēng)加密:瀏覽器發(fā)送對(duì)稱(chēng)加密套件列表、非對(duì)稱(chēng)加密列表和隨機(jī)數(shù) client-random 給服務(wù)器,服務(wù)器生成隨機(jī)數(shù) service-random,選擇加密套件和公鑰返回給瀏覽器,瀏覽器利用 client-random 和 service-random 計(jì)算出 pre-master,然后利用公鑰給 pre-master 加密,向服務(wù)器發(fā)送加密后的數(shù)據(jù),服務(wù)器用私鑰解密出 pre-master 數(shù)據(jù),結(jié)合 client-random 和 service-random 生成對(duì)稱(chēng)密鑰,使用對(duì)稱(chēng)密鑰傳輸加密數(shù)據(jù);
引入數(shù)字證書(shū)是為了證明“我就是我”,防止 DNS 被劫持,偽造服務(wù)器;
證書(shū)的作用:一個(gè)是向?yàn)g覽器證明服務(wù)器的身份,另一個(gè)是包含服務(wù)器公鑰;
數(shù)字簽名過(guò)程:CA 使用 Hash 函數(shù)技術(shù)明文信息,得出信息摘要,然后 CA 使用私鑰對(duì)信息摘要進(jìn)行加密,加密后的秘文就是數(shù)字簽名;
驗(yàn)證數(shù)字簽名:讀取證書(shū)明文信息,使用相同 Hash 函數(shù)計(jì)算得到信息摘要 A,再利用 CA 的公鑰解密得到 B,對(duì)比 A 和 B,如果一致,則確認(rèn)證書(shū)合法;
點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開(kāi)更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)“回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~
- END -

