瀏覽器工作原理

作者:Arika O

原文:https://dev.to/arikaturika/how-web-browsers-work-part-1-with-illustrations-1nid

譯者:superZidan

譯文:https://juejin.cn/post/7204806134935306301

瀏覽器(也稱爲網絡瀏覽器或互聯網瀏覽器)是安裝在我們設備上的軟件應用程序,使我們能夠訪問萬維網。在閱讀這篇文字時,你實際上正在使用一個瀏覽器。

但是,它們實際上是如何工作的,從我們在地址欄中鍵入網絡地址開始,到我們試圖訪問的頁面顯示在屏幕上,會發生什麼?

關於這個問題的答案,一個極其簡化的版本是:

當我們從一個特定的網站請求一個網頁時,瀏覽器從網絡服務器檢索必要的內容,然後在我們的設備上顯示該網頁。

很直接,對嗎?是的,但在這個看似超級簡單的過程中還涉及更多的內容。在這個系列中,我們將討論導航獲取數據解析渲染等步驟,並希望能使你對這些概念更清晰。

  1. 導航 =====

導航是加載網頁的第一步。它指的是當用戶通過點擊一個鏈接在瀏覽器地址欄中寫下一個網址提交一個表格等方式請求一個網頁時發生的過程。

DNS 查詢(解決網址問題)

導航到一個網頁的第一步是找到該網頁的靜態資源位置(HTML、CSS、Javascript 和其他類型的文件)。如果我們導航到 example.com  ,HTML 頁面位於 IP 地址爲 93.184.216.34 的服務器上(對我們來說,網站是域名,但對計算機來說,它們是 IP 地址)。如果我們以前從未訪問過這個網站,就必須進行域名系統(DNS)查詢。微信搜索公衆號:架構師指南,回覆:架構師 領取資料 。

DNS 服務器是包含公共 IP 地址及其相關主機名數據庫的計算機服務器(這通常被比作電話簿,因爲人們的名字與一個特定的電話號碼相關聯)。在大多數情況下,這些服務器按照要求將這些名字解析或翻譯成 IP 地址(現在有 600 多個不同的 DNS 根服務器分佈在世界各地)。

因此,當我們請求進行 DNS 查詢時,我們實際做的是與這些服務器中的一個進行對話,要求找出與 example.com 名稱相對應的 IP 地址。如果找到了一個對應的 IP,就會返回。如果發生了一些情況,查找不成功,我們會在瀏覽器中看到一些錯誤信息。

在這個最初的查詢之後,IP 地址可能會被緩存一段時間,所以下次訪問同一個網站會更快,因爲不需要進行 DNS 查詢(記住,DNS 查詢只發生在我們第一次訪問一個網站時)。

TCP (Transmission Control Protocol) 握手

一旦瀏覽器知道了網站的 IP 地址,它將嘗試通過 TCP 三次握手(也稱爲 SYN-SYN-ACK,或者更準確的說是 SYN、SYN-ACK、ACK,因爲 TCP 有三個消息傳輸,用於協商和啓動兩臺計算機之間的 TCP 會話),與持有資源的服務器建立連接。

TCP 是傳輸控制協議的縮寫,是一種通信標準,使應用程序和計算設備能夠在網絡上交換信息。它被設計用來在互聯網上發送數據包,並確保數據和信息在網絡上成功傳遞。

TCP 握手是一種機制,旨在讓兩個想要相互傳遞信息的實體(在我們的例子中是瀏覽器和服務器)在傳輸數據之前協商好連接的參數。

因此,如果瀏覽器和服務器是兩個人,他們之間的對話會是這樣的:

瀏覽器向服務器發送一個 SYNC 消息,要求進行同步(同步意味着連接)

然後,服務器將回復一個 SYNC-ACK 消息( SYNChronization 和 ACKnowledgement)

在最後一步,瀏覽器將回復一個 ACK 信息

現在,TCP 連接(雙向連接)已經通過 3 次握手建立,TLS 協商可以開始。

TLS 協商

對於通過 HTTPS 建立的安全連接,需要進行另一次握手。這種握手(TLS 協商)決定了哪個密碼將被用於加密通信,驗證服務器,並在開始實際的數據傳輸之前建立一個安全的連接。

傳輸層安全(TLS)是現已廢棄的安全套接字層(SSL)的繼任者,是一種加密協議,旨在通過計算機網絡提供通信安全。該協議被廣泛用於電子郵件和即時通訊等應用,但它在確保 HTTPS 安全方面的應用仍然是最公開的。由於應用程序可以使用或不使用 TLS(或 SSL)進行通信,因此客戶(瀏覽器)有必要要求服務器建立 TLS 連接。

在這一步驟中,瀏覽器和服務器之間還交換了一些信息

  1. 客戶端 hello。瀏覽器向服務器發送一條信息,其中包括它所支持的 TLS 版本和密碼套件,以及一串隨機字節,稱爲 客戶端隨機數

  2. 服務器 hello 和證書。服務器發回一條信息,其中包括服務器的 SSL 證書、服務器選擇的密碼套件和服務器隨機數,這是服務器生成的另一個隨機字節串。

  3. 認證。瀏覽器會向頒發證書的機構覈實服務器的 SSL 證書。這樣,瀏覽器就可以確定服務器就是它所說的那個人。

  4. 預主密碼。瀏覽器會再發送一串隨機的字節,稱爲主密鑰,用瀏覽器從服務器的 SSL 證書上獲取的公鑰進行加密。主密碼只能由服務器用私鑰解密。

  5. 使用私鑰。服務器解密預主密碼

  6. 創建會話密鑰。瀏覽器和服務器從客戶端隨機數、服務器隨機數和預主密碼中生成會話密鑰。

  7. 客戶端完成。瀏覽器向服務器發送一個消息,說它已經完成。

  8. 服務器完成。服務器向瀏覽器發送一個消息,表示它也完成了。

  9. 安全對稱加密實現。握手完成,通信可以繼續使用會話密鑰。

現在可以開始從服務器請求和接收數據了

  1. 獲取數據 =======

在上一節中,我們談到了導航,這是瀏覽器顯示網站的第一步。現在,我們將進入下一個步驟,看看如何獲取資源

HTTP 請求

在我們與服務器建立安全連接後,瀏覽器將發送一個初始的 HTTP GET 請求。首先,瀏覽器將請求頁面的 HTML 文件。它將使用 HTTP 協議來做這件事。

HTTP(超文本傳輸協議)是一個獲取資源的協議,如 HTML 文件。它是網絡上任何數據交換的基礎,它是一個客戶 - 服務器協議,這意味着請求是由接收者發起的,通常是網絡瀏覽器。

請求方法 - POST, GET, PUT, PATCH, DELETE 等

URI - 是統一資源識別符的縮寫。URIs 用於識別互聯網上的抽象或物理資源,如網站或電子郵件地址等資源。一個 URI 最多可以有 5 個部分

scheme:用於說明使用的是什麼協議

authority:用於識別域名

path:用於顯示資源的確切路徑

query:用於表示一個請求動作

fragment:用來指代資源的一部分

// URI parts
scheme :// authority path ? query # fragment

//URI example
<https://example.com/users/user?name=Alice#address>

https: // scheme name
example.com // authority
users/user // path
name=Alice // query
address // fragment

HTTP 頭字段 - 是瀏覽器和服務器在每個 HTTP 請求和響應中發送和接收的字符串列表(它們通常對終端用戶是不可見的)。在請求的情況下,它們包含關於要獲取的資源或請求資源的瀏覽器的更多信息。

如果你想看看這些請求頭字段是什麼樣子的,請進入 Chrome 瀏覽器並打開開發者工具(F12)。進入 Network 標籤,選擇 FETCH/XHR。在下面的屏幕截圖中,我剛剛在搜索引擎上搜索了Palm Springs,這就是請求頭的樣子。

HTTP 響應

一旦服務器收到請求,它將對其進行處理並回復一個 HTTP 響應。在響應的正文中,我們可以找到所有相關的響應頭和我們請求的 HTML 文檔的內容

狀態代碼 - 例如:200、400、401、504 網關超時等(我們的目標是 200 狀態代碼,因爲它告訴我們一切正常,請求是成功的)

響應頭字段 - 保存關於響應的額外信息,如它的位置或提供它的服務器。

一個 HTML 文檔的例子可以是這樣的

<!doctype HTML>
<html>
 <head>
  <meta charset="UTF-8">
    <meta >
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>我的頁面</title>
  <link rel="stylesheet" src="styles.css"/>
  <script src="mainScripts.js"></script>
</head>
<body>
  <h1 class="heading">這個是我的頁面</h1>
  <p>一個段落和一個 <a href="<https://example.com/about>">鏈接</a></p>
  <div>
    <img src="myImage.jpg" alt="image description"/>
  </div>
  <script src="sideEffectsScripts.js"></script>
</body>
</html>

對於我前面提到的同一個搜索,響應頭是這樣的

如果我們看一下 HTML 文檔,我們會發現它引用了不同的 CSS 和 Javascript 文件。這些文件不會被請求。在這個時候,只有 HTML 被請求並從服務器接收。

這個初始請求的響應包含收到的第一個字節的數據。第一個字節的時間(TTFB)是指從用戶提出請求(在地址欄中輸入網站名稱)到收到第一個 HTML 數據包(通常爲 14kb)的時間。

TCP 慢啓動和擁塞算法

TCP 慢啓動 是一種平衡網絡連接速度的算法。第一個數據包將是 14kb(或更小),其工作方式是逐漸增加傳輸的數據量,直到達到預定的閾值。從服務器接收到每個數據包後,客戶端以 ACK 消息響應。由於連接容量有限,如果服務器發送太多數據包太快,它們將被丟棄。客戶端不會發送任何 ACK 消息,因此服務器會將此解釋爲擁塞。這就是擁塞算法發揮作用的地方。他們監控發送的數據包和 ACK 消息的流,以確定流量的最佳速率並創建穩定的流量流。

3.HTML 解析

到目前爲止,我們討論了導航和數據獲取。今天我們將討論解析,特別是 HTML 解析

我們看到在向服務器發出初始請求後,瀏覽器如何收到包含我們嘗試訪問的網頁的 HTML 資源(第一塊數據)的響應。現在瀏覽器的工作就是開始解析數據。

解析是指將程序分析並轉換爲運行時環境實際可以運行的內部格式

換句話說,解析意味着將我們編寫的代碼作爲文本(HTML、CSS)並將其轉換爲瀏覽器可以使用的內容。解析將由瀏覽器引擎完成(不要與瀏覽器的 Javascript 引擎混淆)。

瀏覽器引擎是每個主要瀏覽器的核心組件,它的主要作用是結合結構 (HTML) 和樣式 (CSS),以便它可以在我們的屏幕上繪製網頁。它還負責找出哪些代碼片段是交互式的。我們不應將其視爲一個單獨的軟件,而應將其視爲更大軟件(在我們的例子中爲瀏覽器)的一部分。

有許多瀏覽器引擎,但大多數瀏覽器使用這三個活躍且完整引擎之一:

Gecko 它是由 Mozilla 爲 Firefox 開發的。過去,它曾爲其他幾種瀏覽器提供支持,但目前,除了 Firefox,Tor 和 Waterfox 是唯一仍在使用 Gecko 的瀏覽器。它是用 C++ 和 JavaScript 編寫的,自 2016 年起,還用 Rust 編寫。

WebKit 它主要由 Apple 爲 Safari 開發。它還爲 GNOME Web (Epiphany) 和 Otter 提供支持。(令人驚訝的是,在 iOS 上,包括 Firefox 和 Chrome 在內的所有瀏覽器也由 WebKit 提供支持)。它是用 C++ 編寫的。

Blink,Chromium 的一部分 它最初是 WebKit 的一個分支,主要由 Google 爲 Chrome 開發。它還爲 Edge、Brave、Silk、Vivaldi、Opera 和大多數其他瀏覽器項目(一些通過 QtWebEngine)提供支持。它是用 C++ 編寫的。

現在我們瞭解了誰將進行解析,讓我們看看在從服務器接收到第一個 HTML 文檔後到底發生了什麼。讓我們假設文檔如下所示:

<!doctype HTML>
<html>
 <head>
  <title>This is my page</title>
  <meta charset="UTF-8">
  <meta >
</head>
<body>
  <h1>This is my page</h1>
  <h3>This is a H3 header.</h3>
  <p>This is a paragraph.</p>
  <p>This is another paragraph,</p>
</body>
</html>

即使請求頁面的 HTML 大於初始的 14KB 數據包,瀏覽器也會開始解析並嘗試根據其擁有的數據呈現體驗。HTML 解析涉及兩個步驟:詞法分析 和 樹構造(構建稱爲 DOM 樹的東西)。

詞法分析

它將一些輸入轉換爲標籤(源代碼的基本組件)。想象一下,我們將一段英文文本分解成單詞,其中單詞就是標籤。

詞法分析過程結束時的結果是一系列 0 個或多個以下標籤:DOCTYPE、開始標籤 (<tag>)、結束標籤 (</tag>)、自閉合標籤 (<tag/>) 、屬性名稱、值、註釋、字符、文件結尾或元素中的純文本內容。

構建 DOM

創建第一個 token 後,樹構建開始。這實質上是基於先前解析的標籤創建樹狀結構(稱爲文檔對象模型)。

DOM 樹描述了 HTML 文檔的內容。<html> 元素是文檔樹的第一個標籤和根節點。樹反映了不同標籤之間的關係和層次結構。我們有父節點,嵌套在其他標籤中的標籤是子節點。節點數越多,構建 DOM 樹所需的時間就越長。下面是我們從服務器獲得的 HTML 文檔示例的 DOM 樹:

實際上,DOM 比我們在該模式中看到的更復雜,但我保持簡單以便更好地理解。

此構建階段是可重入的,這意味着在處理一個 token 時,分詞器可能會恢復,導致在第一個 token 處理完成之前觸發並處理更多 token。從字節到創建 DOM,整個過程如下所示:

解析器從上到下逐行工作。當解析器遇到非阻塞資源(例如圖像)時,瀏覽器會向服務器請求這些圖像並繼續解析。另一方面,如果它遇到阻塞資源(CSS 樣式表、在 HTML 的 部分添加的 Javascrpt 文件或從 CDN 添加的字體),解析器將停止執行,直到所有這些阻塞資源都被下載。這就是爲什麼,如果你正在使用 Javascript,建議在 HTML 文件的末尾添加

預加載器 & 使頁面更快

Internet Explorer、WebKit 和 Mozilla 都在 2008 年實現了預加載器,作爲處理阻塞資源的一種方式,尤其是腳本(我們之前說過,當遇到腳本標籤時,HTML 解析將停止,直到腳本被下載並執行)。

使用預加載器,當瀏覽器卡在腳本上時,第二個較輕的解析器會掃描 HTML 以查找需要檢索的資源(樣式表、腳本等)。然後預加載器開始在後臺檢索這些資源,目的是在主 HTML 解析器到達它們時它們可能已經被下載(如果這些資源已經被緩存,則跳過此步驟)。

  1. 解析 CSS =========

解析完 HTML 之後,就該解析 CSS(在外部 CSS 文件和樣式元素中找到)並構建 CSSOM 樹(CSS 對象模型)。

當瀏覽器遇到 CSS 樣式表時,無論是外部樣式表還是嵌入式樣式表,它都需要將文本解析爲可用於設置佈局樣式的內容。瀏覽器將 CSS 變成的數據結構稱爲 CSSOM。DOM 和 CSSOM 遵循相似的概念,因爲它們都是樹,但它們是不同的數據結構。就像從我們的 HTML 構建 DOM 一樣,從 CSS 構建 CSSOM 被認爲是一個「渲染阻塞 」過程。

詞法分析和構建 CSSOM

與 HTML 解析類似,CSS 解析從詞法分析開始。CSS 解析器獲取字節並將它們轉換爲字符,然後是標籤,然後是節點,最後它們被鏈接到 CSSOM 中。瀏覽器會執行一些稱爲選擇器匹配的操作,這意味着每組樣式都將與頁面上的所有節點(元素)匹配。

瀏覽器從適用於節點的最通用規則開始(例如:如果節點是 body 元素的子節點,則所有 body 樣式都由該節點繼承),然後通過應用更具體的規則遞歸地優化計算出的樣式。這就是爲什麼我們說樣式規則是級聯的。

假設我們有下面的 HTML 和 CSS:

body {
  font-size: 16px;
  color: white;
} 

h1 {
  font-size: 32px;
}

section {
  color: tomato;
}

section .mainTitle {
  margin-left: 5px
}

div {
  font-size: 20px;
}

div p {
  font-size:  8px;
  color: yellow;
}

這段代碼的 CSSOM 看起來像這樣:

請注意,在上面的模式中,嵌套元素既有繼承的樣式(來自父級 - 例如:h1 從 body 繼承其顏色,section 從 body 繼承其字體大小)和它們自己的樣式(可以覆蓋繼承的規則 是否來自父節點 - 例如:p 覆蓋了從 div 繼承的顏色和字體大小,而 mainTitle 沒有從父節點獲得其左邊距)。

由於我們的 CSS 可以有多個來源,並且它們可以包含適用於同一節點的規則,因此瀏覽器必須決定最終應用哪個規則。這就是優先級發揮作用的時候,如果您想了解更多相關信息,可以訪問 https://developer.mozilla.org/zh-CN/docs/Web/CSS/Specificity。

想象一下,您在機場尋找您的朋友 John。如果你想通過喊他的名字找到他,你可以喊 “John”。可能不止一個 John 會同時出現在機場,所以他們可能都會做出迴應。更好的方法是用他的全名打電話給你的朋友,這樣當你喊 “John Doe” 時,你就有更好的機會找到他,因爲 “ John Doe ” 比“ John ”更具體。

同樣,假設我們有這個元素:

<p>
  <a href="<https://dev.to/>">This is just a link!</a>
</p>

以及這些 CSS 樣式:

{
   color: red;
}

p  a {
   color: blue;
}

您認爲瀏覽器會應用哪條規則?答案是第二條規則,因爲 p 標籤中的所有 a 標籤選擇器比所有 a 標籤選擇器都具有更高的優先級。如果你想玩玩優先級,你可以使用這個 優先級計算器 ( https://specificity.keegan.st/ )。

重點

CSS 規則是從右到左閱讀的,這意味着如果我們有這樣的代碼:section p { color: blue; }, 瀏覽器將首先查找頁面上的所有 p 標籤,然後它會查看這些 p 標籤中是否有一個 section 標籤作爲父標籤。如果查找能夠命中,它將應用這個 CSS 規則

  1. 執行 Javascript ================

在解析 CSS 並創建 CSSOM 的同時,還會下載其他資產,包括 JavaScript 文件。這要歸功於我們在之前文章中提到的預加載器。

預加載器就像一個解析器,它在主解析器處理 HTML 代碼時掃描 HTML 文件。它的作用是查找樣式表、腳本或圖片(也需要從服務器檢索)等資源並請求它們。希望在解析 HTML 時,這些資源已經下載並準備好進行處理。

所以,當我們從服務器獲取 Javascript 文件後,代碼被解釋、編譯、解析和執行。計算機無法理解 Javascript 代碼,只有瀏覽器可以。JS 代碼需要被翻譯成計算機可以使用的東西,這是 Javascript 瀏覽器引擎的工作(不要與瀏覽器引擎混淆)。根據瀏覽器的不同,JS 引擎可以有不同的名稱和不同的工作方式。

Javascript 引擎

javascript 引擎(有時也稱爲 ECMAScript 引擎)是一種在瀏覽器中執行(運行)Javascript 代碼的軟件,而不僅僅是零部件(例如,V8 引擎是 Node.js 環境的核心組件)。

JavaScript 引擎通常由 Web 瀏覽器供應商開發,每個主要瀏覽器都有一個。我們說過,目前使用最多的瀏覽器是 Chrome、Safari、Edge 和 Firefox。每個都使用不同的 Javascript 引擎,它們是:

V8 V8 是 Google 的高性能 JavaScript 引擎。它是用 C++ 編寫的,用於 Chrome 和 Node.js 等。它實現了 ECMAScript(一種 JavaScript 標準,旨在確保網頁在不同 Web 瀏覽器之間的互操作性)和 WebAssembley。它實現了 ECMA-262。

JavaScriptCore JavaScriptCore 是 WebKit 的內置 JavaScript 引擎,它爲 Safari 瀏覽器、郵件和 macOS 上使用的其他應用程序提供支持。它目前按照 ECMA-262 規範實現 ECMAScript。它也被稱爲 SquirrelFish 或 SquirrelFish Extreme。

Chakra Chakra 是微軟爲其 Microsoft Edge 網絡瀏覽器和其他 Windows 應用程序開發的 Javascript 引擎。它實現了 ECMAScript 5.1,並且對 ECMAScript 6 有部分(不斷增加的)支持。它是用 C++ 編寫的。

SpiderMonkey SpiderMonkey 是 Mozilla 的 Javascript 和 WebAssembly 引擎。它是用 C++、Javascript 和 Rust 編寫的,用於爲 Firefox、Servo 和其他項目提供支持。

一開始,Javascript 引擎只是簡單的解釋器。我們今天使用的現代瀏覽器能夠執行稱爲即時 (JIT) 編譯的功能,這是編譯和解釋的混合體。

編譯

在編譯過程中,一個稱爲編譯器的軟件將用高級語言編寫的代碼一次性轉換爲機器代碼。創建一個目標文件,該文件可以在任何機器上運行。採取這些步驟後,就可以執行代碼了。

解釋

在解釋過程中,解釋器逐行檢查 Javascript 代碼並立即執行。沒有進行編譯,因此沒有創建目標代碼(代碼的輸出由解釋器本身使用其內部機制創建)。舊版本的 Javascript 使用這種類型的代碼執行。

5-2.webp

即時編譯 ( JIT Compilation )

即時編譯是給定語言的解釋器的一個特性,它試圖同時利用編譯和解釋。是在純編譯期間,代碼是在執行之前被編譯,然而在 JIT 編譯中,代碼在執行時(在運行時)被編譯。所以我們可以說源代碼是動態轉換爲機器代碼的。較新版本的 Javascript 使用這種類型的代碼執行。

JIT 編譯的一個很重要的方面就是將源代碼編譯成當前正在運行的機器的機器碼指令。這意味着生成的機器代碼是針對正在運行的機器的 CPU 架構進行了優化。

簡而言之,這三個過程可以總結爲:

今天,編譯解釋這兩個術語之間的界限已經變得非常模糊,因此這個主題可以進行廣泛的辯論。如果你想了解更多關於這些過程的信息,你可以閱讀這篇關於 Mozilla Hacks for starters(https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/) 的文章。

請注意,我提到了舊版本和新版本的 Javascript。不支持較新版本語言的瀏覽器將解釋代碼,而支持的瀏覽器將使用某些版本的 JIT 來執行代碼(V8、Chakra JavaScriptCore 和 SpiderMonkey 引擎都使用 JIT)。事實上,儘管 Javascript 是一種解釋型語言(它不需要編譯),但如今大多數瀏覽器都會使用 JIT 編譯來運行代碼,而不是純粹的解釋型語言。

Javascript 代碼是如何處理的

當 Javascript 代碼進入 Javascript 引擎時,它首先被解析。這意味着代碼被讀取,並且在這種情況下,代碼被轉換爲稱爲抽象語法樹 (AST) 的數據結構。代碼將被拆分成與語言相關的部分(如 function 或 const 關鍵字),然後所有這些部分將構建抽象語法樹。

假設我們有一個文件,其中包含一個只做一件事的程序,那就是定義一個變量:

const age = 25;

這就是這行非常簡單的代碼看起來像抽象語法樹的方式(我正在使用 @babel/parser-7.16.12):

如果你想將一些 Javascript 轉換爲抽象語法樹,你可以使用這個工具(https://astexplorer.net/)。編寫變量後得到的 AST 實際上要大得多,在屏幕截圖中隱藏了更多節點。

構建 AST 後,它會被翻譯成機器代碼並立即執行,因爲現代 Javascript 使用即時編譯。這段代碼的執行將由 Javascript 引擎完成,利用稱爲 “調用堆棧” 的東西。

調用堆棧是解釋器(如 Web 瀏覽器中的 JavaScript 解釋器)跟蹤其在調用多個函數的腳本中的位置的機制——當前正在運行的函數以及從該函數中調用的函數等。

  1. 創建可訪問(無障礙)樹 ==============

除了我們一直在討論的所有這些樹(DOM、CSSOM 和 AST)之外,瀏覽器還構建了一種稱爲可訪問(無障礙)樹的東西。

Web 開發中的可訪問性(通常縮寫爲 A11y — 如 “a”,然後是 11 個字符,然後是 “y”)意味着讓儘可能多的人能夠使用網站,即使這些人的能力在某種程度上受到限制。對很多人來說,技術讓事情變得更容易。對於殘障人士,技術使事情成爲可能。可訪問性意味着開發儘可能易於訪問的內容,無論個人的身體和認知能力以及他們如何訪問網絡 (ACT)。

一般而言,殘疾用戶可以並且確實在使用具有各種輔助技術的網頁。他們使用屏幕閱讀器、放大鏡、眼動追蹤、語音命令等。爲了讓這些技術發揮作用,它們需要能夠訪問頁面的內容。由於他們無法直接讀取 DOM,因此 ACT 開始發揮作用。

可訪問性樹是使用 DOM 構建的,稍後輔助設備將使用它來解析和解釋我們正在訪問的網頁的內容。ACT 就像 DOM 的語義版本,每次 DOM 更新時它都會更新。每個需要暴露給輔助技術的 DOM 元素都會在 ACT 中有一個對應節點。在未構建 ACT 之前,屏幕閱讀器無法訪問內容。

要查看可訪問性樹的實際的樣子,您可以通過 Google Chrome 瀏覽器。打開調試器 (F12) 並轉到 “元素” 選項卡。從那裏,你可以在右側選擇 “輔助功能” 窗格。

我去 Google 並檢查了搜索輸入,這是我在 “計算” 屬性下的 “輔助功能” 窗格中得到的:

使用語義 HTML 的重要性超出了本文的範圍,但作爲開發人員,我們都應該記住,我們構建的網站應該可供所有希望使用它們的人使用。如果您想閱讀有關該主題的更多信息,可以在 https://www.w3.org/WAI/fundamentals/accessibility-intro/zh-hans 找到一篇關於 Web 可訪問性的很好的介紹性文章。據互聯網協會無障礙訪問特別興趣小組(https://www.a11ysig.org/) 稱,目前全世界有超過 13 億人(約佔世界人口的 15%)患有某種形式的殘疾。

  1. 渲染樹 ======

在解析階段構建的樹(DOM、CSSOM)被組合成一種叫做渲染樹的東西。這用於計算最終將繪製到屏幕上的所有可見元素的佈局。渲染樹的目的是確保頁面內容以正確的順序繪製元素。它將作爲在屏幕上顯示像素的繪畫過程的輸入。

DOM 和 CSSOM 是使用 HTML 和 CSS 文件創建的。這兩個文件包含不同類型的信息,樹的結構也不同,那麼渲染樹是如何創建的呢?

結合 DOM 和 CSSOM

以上步驟的結果將是一個包含所有可見節點、內容和樣式的渲染樹

佈局(迴流)階段

渲染樹包含有關顯示哪些節點及其計算樣式的信息,但不包含每個節點的尺寸或位置。

接下來需要做的是計算這些節點在設備視口(瀏覽器窗口內)內的確切位置及其大小。這個階段稱爲佈局(在 Chrome、Opera、Safari 和 Internet Explorer 中)或重排(在 Firefox 中),但它們的意思相同。瀏覽器在渲染樹的根部開始這個過程並遍歷它。

迴流步驟不會只發生一次,而是每次我們更改 DOM 中影響頁面佈局的某些內容時,即使是部分更改,都會觸發迴流。重新計算元素位置的情況示例如下:

讓我們來看一個非常基本的 HTML 示例,其中內嵌了一些 CSS:

<!DOCTYPE html>
<html>
  <head>
    <meta  />
    <title>Reflow</title>
  </head>
  <body>
    <div style="width: 100%; height: 50%">
      <div style="width: 50%; height: 50%">This is the reflow stage!</div>
    </div>
  </body>
</html>

上面的代碼只是說在視口內我們應該有兩個 div,其中第二個嵌套在第一個裏面。父 div 佔據視口寬度的 100% 和高度的 50%。第二個 div 佔據父 div 的 50% 這看起來像這樣:

這個過程的輸出是一個類似盒子的模型,它準確地捕獲了每個元素需要在屏幕上的位置及其大小。完成此步驟後,輸出就可以傳遞到下一步,稱爲繪畫階段

繪畫(重繪)階段

在瀏覽器決定哪些節點需要可見並計算出它們在視口中的位置後,就可以在屏幕上繪製它們(渲染像素)了。這個階段也被稱爲光柵化階段,瀏覽器將在佈局階段計算的每個盒子轉換爲屏幕上的實際像素。

就像佈局階段一樣,繪畫階段不會只發生一次,而是每次我們改變屏幕上元素的外觀時。這些情況的例子是:

繪畫意味着瀏覽器需要將元素的每個視覺部分繪製到屏幕上,包括文本、顏色、邊框、陰影和替換元素(如按鈕和圖像),並且需要超快地完成。爲了確保重繪可以比初始繪製更快地完成,屏幕上的繪圖通常被分解成幾層。如果發生這種情況,則需要進行合成。

分層和合成

傳統意義上,網絡瀏覽器完全依賴 CPU 來呈現網頁內容。但現在即使是最小的設備也有高性能的 GPU,所有大部分實現方案都圍繞着 GPU 來尋求更好的體驗。

合成是一種將頁面的各個部分分成層的技術,分別繪製它們並在稱爲合成器線程的單獨線程中合成爲頁面。當文檔的各個部分繪製在不同的層中並相互重疊時,合成是必要的,以確保它們以正確的順序繪製到屏幕上並且內容被正確呈現。

通常,只有特定的任務會被重定向到 GPU,而這些任務可以由合成器線程單獨處理。

爲了找出哪些元素需要在哪一層,主線程遍歷佈局樹並創建層樹。默認情況下,只有一層(這些層的實現方式因瀏覽器而異),但我們可以找到會觸發重繪的元素,併爲每個元素創建一個單獨的層。這樣,重繪不應應用於整個頁面,而且此過程將可以使用到 GPU

如果我們想向瀏覽器提示某些元素應該在一個單獨的層上,我們可以使用 will-change CSS 屬性。實際上有一些特定的屬性和元素表示新層的創建。其中一些是 <video><canvas> 和任何具有 CSS opacity 屬性、3D transformwill-change 和其他一些屬性的元素。這些節點連同它們的後代將被繪製到它們自己的圖層上。

謹記

上面討論的兩種操作,迴流和重繪,都是昂貴的,尤其是在像手機這樣處理能力低的設備上。這就是爲什麼在處理 DOM 更改時我們應該嘗試優化它們。有些動作只會觸發重繪,有些動作會同時觸發迴流和重繪。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/L4p4LZPfm6kjTIg66E6tYg