前面一篇文章:「高頻面試題」瀏覽器從輸入url到頁面展示中間發生了什么 中,我們有對瀏覽器的渲染流程做了一個概括性的介紹,今天這篇文章我們將深入學習這部分內容。
對于很多前端開發來說,平常做工主要專注于業務開發,對瀏覽器的渲染階段可能不是很了解。實際上這個階段很重要,了解瀏覽器的渲染過程,能讓我們知道我們寫的HTML、CSS、JS代碼是如何被解析,并最終渲染成一個頁面的,在頁面性能優化的時候有相應的解決思路。
我們先來看一個問題:
HTML、CSS、JS文件在瀏覽器中是如何轉化成頁面的?
如果你回答不上來,那就往下看吧。
按照渲染的時間順序,渲染過程可以分為下面幾個子階段:構建DOM樹、樣式計算、布局階段、分層、柵格化和合成顯示。
下面詳細看下每個階段都做了哪些事情。
HTML文檔描述一個頁面的結構,但是瀏覽器無法直接理解和使用HTML,所以需要通過HTML解析器將HTML轉換成瀏覽器能夠理解的結構——DOM樹。
HTML文檔中所有內容皆為節點,各節點之間有層級關系,彼此相連,構成DOM樹。
構建過程:讀取HTML文檔的字節(Bytes),將字節轉換成字符(Chars),依據字符確定標簽(Tokens),將標簽轉換成節點(Nodes),以節點為基準構建DOM樹。參考下圖:
打開Chrome的開發者工具,在控制臺輸入 document 后回車,就能看到一個完整的DOM樹結構,如下圖所示:
在控制臺打印出來的DOM結構和HTML內容幾乎一樣,但和HTML不同的是,DOM是保存在內存中的樹狀結構,可以通過JavaScript來查詢或修改其內容。
樣式計算這個階段,是為了計算出DOM節點中每個元素的表現樣式。
CSS樣式可以通過下面三種方式引入:
和HTML一樣,瀏覽器無法直接理解純文本的CSS樣式,需要通過CSS解析器將CSS解析成 styleSheets 結構,也就是我們常說的 CSSOM樹。
styleSheets結構同樣具備查詢和修改功能:
document.styleSheets
屬性值標準化看字面意思有點不好理解,我們通過下面一個例子來看看什么是屬性值標準化:
在寫CSS樣式的時候,我們在設置color屬性值的時候,經常會用white、red等,但是這種值瀏覽器的渲染引擎不容易理解,所以需要將所有值轉換成渲染引擎容易理解的、標準化的計算值,這個過程就是屬性值標準化。
white標準化后的值為 rgb(255, 255, 255)
完成樣式的屬性值標準化后,就需要計算每個節點的樣式屬性,這個階段CSS有兩個規則我們需要清楚:
樣式計算階段是為了計算出DOM節點中每個元素的具體樣式,在計算過程中需要遵守CSS的繼承和層疊兩個規則。
該階段最終輸出的內容是每個DOM節點的樣式,并被保存在 ComputedStyle 的結構中。
經過上面的兩個步驟,我們已經拿到了DOM樹和DOM樹中元素的樣式,接下來需要計算DOM樹中可見元素的幾何位置,這個計算過程就是布局。
在DOM樹中包含了一些不可見的元素,例如 head 標簽,設置了 display:none 屬性的元素,所以我們需要額外構建一棵只包含可見元素的布局樹。
構建過程:從DOM樹的根節點開始遍歷,將所有可見的節點加到布局樹中,忽略不可見的節點。
到這里我們就有了一棵構建好的布局樹,就可以開始計算布局樹節點的坐標位置了。從根節點開始遍歷,結合上面計算得到的樣式,確定每個節點對象在頁面上的具體大小和位置,將這些信息保存在布局樹中。
布局階段的輸出是一個盒子模型,它會精確地捕獲每個元素在屏幕內的確切位置與大小。
現在我們已經有了布局樹,也知道了每個元素的具體位置信息,但是還不能開始繪制頁面,因為頁面中會有像3D變換、頁面滾動、或者用 z-index 進行z軸排序等復雜效果,為了更方便實現這些效果,渲染引擎還需要為特定的節點生成專用的圖層,并生成一棵對應的圖層樹(LayerTree)。
在Chrome瀏覽器中,我們可以打開開發者工具,選擇 Elements-Layers 標簽,就可以看到頁面的分層情況,如下圖所示:
瀏覽器的頁面實際上被分成了很多圖層,這些圖層疊加后合成了最終的頁面。
到這里,我們構建了兩棵樹:布局樹和圖層樹。下面我們來看下這兩棵樹之間的關系:
正常情況下,并不是布局樹的每個節點都包含一個圖層,如果一個節點沒有對應的圖層,那么這個節點就從屬于父節點的圖層。
那節點要滿足什么條件才會被提升為一個單獨的圖層?只要滿足下面其中一個條件即可:
構建好圖層樹之后,渲染引擎就會對圖層樹中的每個圖層進行繪制。
渲染引擎實現圖層繪制,會把一個圖層的繪制拆分成很多小的繪制指令,然后將這些指令按照順序組成一個繪制列表。
繪制一個圖層時會生成一個繪制列表,這只是用來記錄繪制順序和繪制指令的列表,實際上繪制操作是由渲染引擎中的合成線程來完成的。
通過下圖來看下渲染主線程和合成線程之間的關系:
當圖層的繪制列表準備好后,主線程會把該繪制列表提交給合成線程,合成線程開始工作。
首先合成線程會將圖層劃分為圖塊(tile),圖塊大小通常是 256256 或者 512512。
然后合成線程會按照視口附近的圖塊來優先生成位圖,實際生成位圖的操作是由柵格化來執行的。所謂柵格化,是指將圖塊轉換為位圖。而圖塊是柵格化執行的最小單位。渲染進程維護了一個柵格化的線程池,所有的圖塊柵格化都是在線程池內執行的,運行方式如下圖所示:
一旦所有圖塊都被光柵化,合成線程就會生成一個繪制圖塊的命令——“DrawQuad”,然后將該命令提交給瀏覽器進程。
瀏覽器進程里面有一個名字叫做 viz 的組件,用來接收合成線程發過來的 DrawQuad 命令,然后根據命令執行。 DrawQuad 命令,將其頁面內容繪制到內存中,最后再將內存顯示在屏幕上。
多年開發老碼農福利贈送:網頁制作,網站開發,web前端開發,從最零基礎開始的的HTML+CSS+JavaScript。jQuery,Vue、React、Ajax,node,angular框架等到移動端小程序項目實戰【視頻+工具+電子書+系統路線圖】都有整理,需要的伙伴可以私信我,發送“前端”等3秒后就可以獲取領取地址,送給每一位對編程感興趣的小伙伴
一個完整的渲染流程可以總結如下:
渲染過程中還有兩個我們經常聽到的概念:重排和重繪。在這篇文章中就不細說了,下一篇文章再詳細介紹。
家好,很高興又見面了,我是"高級前端?進階?",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發!
高級前端?進階
本文主要和大家介紹將 JavaScript 轉化為 WebAssembly的工具鏈 javy。在年初,我也確實使用 WebAssembly 將客戶端應用成功移植到了 Web,這也是為什么我一直對 WebAssembly 充滿好奇的原因。我甚至在頭條上開了一個合集《WebAssembly 前沿技術》來專門探討 WebAssembly ,并將持續關注 WebAssembly 的最新動態。
下面是已發布部分文章傳送門:
正如大家所看到的,當我們還在遲疑是否要在日常開發中引入 WebAssembly 的時候,很多優秀的應用、工具已經開始吃 WebAssembly 的紅利了,而且取得了不錯的成就,這可能也是為什么各個瀏覽器廠商、開發者如此熱衷 WebAssembly 的原因吧。
WebAssembly 中的 JavaScript 是最近技術圈發展的產物,但是將 JavaScript 編譯成 WebAssembly 不同于使用 JavaScript 與 WebAssembly 模塊通信。本文重點介紹如何獲取 JavaScript 代碼并將其構建到 WebAssembly 模塊中。
話不多說,直接開始進入正題!
安裝所有依賴項后運行 make,現在應該可以訪問 target/release/javy 中的可執行文件。或者可以運行 make && cargo install --path crates/cli, 運行命令后,將獲得可執行文件的全局安裝。
Javy 底層基于 QuickJS, 用于在 WebAssembly 上運行 JavaScript 代碼。 Javy 獲取 JavaScript 代碼,并在 WebAssembly 嵌入式 JavaScript 運行時中執行它。下面是官方對 Javy 的定位:
Javy: A JavaScript to WebAssembly toolchain
Javy 生成的模塊僅設計為 WASI 并遵循命令模式, 任何輸入都必須通過 stdin 傳遞,任何輸出都將放在 stdout 中,這在從自定義嵌入調用 Javy 模塊時尤為重要。
在像 Wasmtime 這樣的運行時中,wasmtime-wasi 可用于設置輸入和檢索輸出。比如下面的 JS 代碼:
//從標準輸入讀取輸入
const input = readInput();
//使用輸入調用函數
const result = foo(input);
// 將結果寫入標準輸出
writeOutput(result);
// 入口函數
function foo(input) {
return { foo: input.n + 1, newBar: input.bar + '!' };
}
// Read input from stdin
function readInput() {
const chunkSize = 1024;
const inputChunks = [];
let totalBytes = 0;
//讀取所有可用字節
while (1) {
const buffer = new Uint8Array(chunkSize);
// 標準輸入文件描述符
const fd = 0;
const bytesRead = Javy.IO.readSync(fd, buffer);
totalBytes += bytesRead;
if (bytesRead === 0) {
break;
}
inputChunks.push(buffer.subarray(0, bytesRead));
}
// 將輸入組裝成單個 Uint8Array
const { finalBuffer } = inputChunks.reduce(
(context, chunk) => {
context.finalBuffer.set(chunk, context.bufferOffset);
context.bufferOffset += chunk.length;
return context;
},
{ bufferOffset: 0, finalBuffer: new Uint8Array(totalBytes) }
);
return JSON.parse(new TextDecoder().decode(finalBuffer));
}
//將輸出寫入標準輸出
function writeOutput(output) {
const encodedOutput = new TextEncoder().encode(JSON.stringify(output));
const buffer = new Uint8Array(encodedOutput);
// 標準輸出文件描述符
const fd = 1;
Javy.IO.writeSync(fd, buffer);
}
通過以下方式從 JavaScript 創建 WebAssembly 二進制文件:
javy compile index.js -o destination/index.wasm
然后,可以使用 WebAssembly 引擎執行 WebAssembly 二進制文件:
$ echo '{ "n": 2, "bar": "baz" }' | wasmtime index.wasm
{"foo":3,"newBar":"baz!"}%
在某些情況下,開發者可能希望或需要使用 Javy 生成更小的 Wasm 模塊。 在調用 Javy 時使用 -d 標志將創建一個動態鏈接模塊,該模塊的文件大小比靜態鏈接模塊小得多。 靜態鏈接模塊將 JS 引擎嵌入模塊內部,而動態鏈接模塊依賴 Wasm 導入來提供 JS 引擎。 動態鏈接模塊有特殊要求,靜態鏈接模塊沒有也不會在不滿足這些要求的 WebAssembly 運行時中執行。
要成功實例化和運行動態鏈接的 Javy 模塊,執行環境必須提供一個 javy_quickjs_provider_v1 命名空間,用于導入該鏈接到 javy_quickjs_provider.wasm 模塊提供的導出。 在不提供此導入的環境中無法實例化動態鏈接的模塊。
動態鏈接的 Javy 模塊與 QuickJS 綁定,因為它們使用 QuickJS 的字節碼表示。
javy_quickjs_provider.wasm 模塊在使用的 Javy 版本中作為資產提供,也可以通過運行 javy emit-provider -o <path> 將模塊寫入 <path> 來獲取。
$ echo 'console.log("hello world!");' > my_code.js
$ javy compile -d -o my_code.wasm my_code.js
$ javy emit-provider -o provider.wasm
$ wasmtime run --preload javy_quickjs_provider_v1=provider.wasm my_code.wasm
hello world!
作為該項目一部分的 quickjs-wasm-rs crate 可以用作針對 Wasm 的 Rust crate 的一部分,以自定義 Rust crate 如何與 QuickJS 交互。
當嘗試在 Wasm 模塊中使用 JavaScript 而 Javy 不滿足您的需求時,這可能很有用,因為 quickjs-wasm-rs 包含序列化程序,可以更輕松地在主機代碼和 Wasm 代碼之間發送結構化數據(例如,字符串或對象)。
本文主要和大家介紹將 JavaScript 轉化為 WebAssembly的工具鏈 javy。因為篇幅有限,文章并沒有過多展開,如果有興趣,可以在我的主頁繼續閱讀,同時文末的參考資料提供了大量優秀文檔以供學習。最后,歡迎大家點贊、評論、轉發、收藏!
https://www.fermyon.com/wasm-languages/javascript
https://blog.csdn.net/tzllxya/article/details/90675984
https://github.com/Shopify/javy
https://www.wasm.builders/gunjan_0307/compiling-javascript-to-wasm-34lk
https://surma.dev/things/compile-js/
https://www.wasm.builders/deepanshu1484/javascript-and-wasi-24k8
https://github.com/ludocode/msgpack-tools
https://www.linkedin.com/pulse/differences-between-static-dynamic-libraries-sergio-monroy
封面圖:Navdeep Singh Gill | 13 October 2022的《WebAssembly vs JavaScript | The Ultimate Guide》
我們在網上發現一篇好文章的時候,將網頁下載到了桌面,但是為了方便編輯,想要將其轉換一下格式,如Word格式。網頁轉換成Word文檔怎么做?其實很多朋友們都想要知道,下面小編就來告訴大家。
針對網頁轉換成Word文檔怎么做這個話題,下面小編以WPS2019為例給大家分享步驟:
第一步:打開WPS軟件,然后在菜單欄里點擊“文件”,然后在下拉菜單選擇“打開”:
第二步:在打開的對話框中,我們點擊下面“文件類型”旁邊下拉列表,選擇“網頁文件(*.html;*.htm)”:
第三步:找到目標文件,并點擊打開:
第三步:選擇菜單欄“文件”,然后點擊下拉菜單“另存為”。在“文件類型”里選擇Word格式,然后點擊保存即可轉換完畢:
看完上面的介紹,相信朋友們對于網頁轉換成Word文檔怎么做都會有所了解。這個操作是比較實用的,大家可以多學習一下。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。