前面一篇文章:「高频面试题」浏览器从输入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小时内与您取得联系。