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