主要功能面板:
网络面板主要有控制器、过滤器、抓图信息、时间线、详细列表和下载信息概要 6 个区域:
将网络传给渲染引擎的 HTML 字节流转换为渲染引擎能够理解的内部结构,这种结构就是 DOM,其提供了对 HTML 文档结构化的表述,在渲染引擎中,DOM 有三个层面的作用:
HTML 解析器(HTML Parser) 负责将 HTML 字节流转换为 DOM 结构。HTML 并不是等整个文档加载完后再解析的,而是 网络进程加载了多少数据,HTML 解析器就解析多少数据。
网络进程收到响应头后,根据响应头中 content-type
字段的值,判断文件类型,如果是 text/html
就会为该请求选择或者创建一个渲染进程。然后网络进程和渲染进程之间建立一个共享数据的管道,网络进程接收数据后通过管道将数据传递给渲染进程,交给 HTML 解析器解析。
字节流转换为 DOM 结构的过程,可以分为三个阶段:
HTML 解析器会维护一个 Token 栈结构,用于计算节点之间的赋值关系,在第一阶段中生成的 Token 会被按顺序压入栈中,具体规则如下:
EndTag div
,HTML 解析器检查栈顶元素是否是 StartTag div
,如果是,则将栈顶元素弹出,表示该 div 元素解析完成通过分词器产生的新 Token 不停进栈和出栈,整个解析过程一直持续,直到分词器将所有字节流分词完成。
<html>
<body>
<div>1</div>
<div>test</div>
</body>
</html>
复制🤏
HTML 解析器开始工作时,会默认创建一个根为 document 的空 DOM 结构,同时将一个 StartTag document
的 Token 压入栈中,然后经过分词器处理,解析出第一个 StartTag html Token
,将其压入栈中,并创建一个 html 的 DOM 节点,添加到 document 上:
然后依次解析 body 和 div:
当解析出 Text Token
时,渲染引擎会为 Text Token
创建一个文本节点,并将其添加到 DOM 树中:
当解析出 EndTag div
时,HTML 解析器会去判断当前栈顶元素是否是 StartTag div
,如果是,则从栈顶弹出 StartTag div
:
最终解析结果如下:
<html>
<body>
<div>1</div>
<script>
let div1 = document.querySelector('div')[0];
div1.innerHTML = 'cellinlab';
</script>
</body>
</html>
当解析到 <script>
标签时,渲染引擎判断是脚本,会暂停 DOM 的解析,因为 JavaScript 可能会修改当前已经生成的 DOM 结构:
HTML 解析器暂停工作后,JavaScript 引擎会介入,并执行 script
标签中的脚本,将 DOM 节点中内容进行修改,脚本执行完后,HTML 解析器恢复解析过程,继续解析后续内容。
如果 JavaScript 是引入的,在执行脚本之前,还需要去下载,由于下载会阻塞 DOM 解析。Chrome 做了一些优化,主要优化是预解析,当渲染引擎收到字节流后,会开启一个预解析线程,用来分析 HTML 文件中包含的 JavaScript、CSS 等相关文件,解析到相关文件后,预解析线程会提前下载这些文件。
可以通过 使用 CDN 、压缩文件大小等方法来加速 JavaScript 的加载,另外,如果 JavaScript 中没有操作 DOM 相关代码,可以设置异步加载,通过 async
或 defer
属性来实现:
async
标志的文件,一旦加载完,会立即执行defer
标志的文件,需要在 DOMContentLoaded 事件之前执行theme.css
div {
color: coral;
background-color: black;
}
<html>
<head>
<link href="theme.css" rel="stylesheet" >
</head>
<body>
<div>cellinlab</div>
</body>
</html>
打开文件时,HTML 文件渲染流水线大致如下:
和 HTML 一样,渲染引擎也是无法直接理解 CSS 文件内容的,所以需要将其解析成渲染引擎能够理解的结构 —— CSSOM(CSSOM 体现在 DOM 中就是 document.styleSheets
),主要有两个作用:
等 DOM 和 CSSOM 都构建好之后,渲染引擎就会构造布局树:
display: none
元素、head
标签、script
标签等对于一些复杂的场景,如:
theme.css
div {
color: coral;
background-color: black;
}
<html>
<head>
<link href="theme.css" rel="stylesheet" >
</head>
<body>
<div>cellinlab</div>
<script>
console.log('cellinlab.xyz');
</script>
<div>cellinlab.xyz</div>
</body>
</html>
由于增加了 JavaScript,渲染流水线会发生一些变化:
在解析 DOM 过程中,如果遇到 JavaScript 脚本,会暂停 DOM 解析去执行 JavaScript,因为 JavaScript 可能会修改当前状态下的 DOM。不过,如果在执行 JavaScript 脚本前,页面中包含了外部 CSS 文件的引用,或者通过 style 标签内置了 CSS 内容,那么渲染引擎还需要将这些内容转换为 CSSOM,因为 JavaScript 有修改 CSSOM 的能力,所以在执行 JavaScript 前,还需要依赖 CSSOM,即 CSS 在部分情况下也会阻塞 DOM 生成。
如果 body 中包含 JavaScript 外部引用文件,会让情况变得更加复杂:
theme.css
div {
color: coral;
background-color: black;
}
foo.js
console.log('cellinlab.xyz');
<html>
<head>
<link href="theme.css" rel="stylesheet" >
</head>
<body>
<div>cellinlab</div>
<script src="foo.js"></script>
<div>cellinlab.xyz</div>
</body>
</html>
渲染流水线影响首次页面展示的速度,首次页面展示速度会直接影响用户体验。通过分析影响首屏展示因素,可以针对性做出优化:
async
或 defer
显示器有固定的刷新频率,通常是 60Hz,可以理解为每秒渲染 60 次,更新前的内容都来自于显卡中的前缓存区。显示器做的任务很简单,就是每秒固定读取 60 次缓存区图像,显示到显示器上。
显卡负责合成新图像,并将图像保存到后缓存区中,一旦显卡将合成图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样能保证显示器能读取到最新显卡合成的图像。通常,显卡的更新频率和显示器一致,有时,在复杂场景,显卡处理速度变慢,会造成视觉上的卡顿。
将渲染流水线生成的每一幅图片称为一帧,把流水线每秒更新了多少帧称为帧率。如,滚动页面过程中,1 秒更新了 60 帧,那帧率就是 60 Hz(或 60 FPS)。
由于用户很容易观察到那些丢失的帧,如果在一次动画过程中,渲染引擎生成某些帧的时间过久,那么用户就会感受到卡顿,会造成不好的用户体验。
要解决卡顿问题,就要解决每帧生成时间过久的问题,为此 Chrome 对浏览器渲染方式做了一些优化,最有成效的策略就是分层和合成机制,这代表了当前最先进的渲染技术。
通常页面组成是非常复杂的,如果没有分层机制,会“牵一发而动全身”严重影响页面渲染效率。为了提升每帧的渲染效率,Chrome 引入了分层和合成的机制。
分层和合成通常一起使用。如,一个页面被分为两层,当进行下一帧的渲染时,前一帧可能需要实现某些变换(平移、缩放、阴影等),此时合成器只需要将两个层进行相应处理,显卡处理这些操作很容易,这样合成过程时间就非常短了。
分层和合成的具体实现:
分层从宏观上提升了渲染效率,分块则是从微观层面提升了渲染效率。
在于元素的几何形状变换、透明度或者缩放操作,如果使用 JavaScript 来写,会牵涉整个渲染流水线,所以 JavaScript 的绘制效率会非常低下。此时,可以使用 will-change
来告知渲染引擎会对该元素进行一些变换,渲染引擎会将该元素单独实现一帧,等这些变换发生时,渲染引擎会通过合成线程去直接处理变换,这些变换不会涉及到主线程,所以效率会提高。这也是 CSS 动画比 JavaScript 动画高效的原因。
.box {
will-change: transform, opacity;
}
当然,每当渲染引擎为一个元素准备一个独立图层的时候,它占用的内存也会大大增加,因为从层树开始,后续每个阶段都会多一个层结构,都会消耗内存,所以需要恰当地使用 will-change
。
主要关于如何让页面更快地显示和响应,一个页面通常分为三个阶段:
典型的渲染流水线:
将能阻塞网页首次渲染的资源称为关键资源,如 JavaScript、首次请求的 HTML、CSS 等。对于关键资源,影响页面首次渲染的因素有:
针对以上情况,主要优化原则就是减少关键资源个数,降低关键资源大小,减少关键资源的 RTT 次数。
async
或 defer
标记交互阶段的优化,实际是关于渲染进程渲染帧的速度,在交互阶段,帧的渲染速度决定交互的流畅度。交互阶段的渲染流水线,没有了加载关键资源和构建 DOM 、CSSOM 流程,通常由 JavaScript 触发交互动画:
大部分情况下,生成一个新的帧是由 JavaScript 通过修改 DOM 或者 CSSOM 来触发的,还有一部分帧是由 CSS 来触发的。
如果在计算样式阶段发现有布局信息的修改,就会触发重排,然后触发渲染流水线系列操作,代价很大。
同样,如果在计算样式阶段发现没有布局信息修改,只是修改颜色一类信息,不涉及布局相关,就会跳过布局阶段,进入绘制阶段,这个过程叫重绘,也会有不小的代价。
还有一种情况,通过 CSS 实现一些变形、渐变、动画等特效,这由 CSS 触发,并且在合成线程上执行,这个过程叫合成。因为不会触发重排或重绘,而且合成操作速度很快,所以执行合成时效率最高的方式。
综上,在交互过程中,优化的主要原则就是让单个帧的生成速度变快,可以从下面入手解决:
offsetWidth
或 offsetHeight
等will-change
将元素抽取单独图层,提高渲染效率通过 JavaScript 操作 DOM 会影响到整个渲染流水线。如,document.body.appendChild(node)
会发生一系列连锁反应,这些操作都会降低渲染效率:
对于简单页面,可以以上操作可能问题不太明显。但是对于一些复杂的页面和项目,DOM 结构非常复杂,而且可能需要不断去修改 DOM 树,每次操作 DOM 渲染引擎都需要进行重排、重绘或合成等操作,由于页面和 DOM 复杂,这些操作会很耗时,带来很大的性能问题。
需要一种方法来减少 JavaScript 对 DOM 的操作,所以有了虚拟 DOM。
虚拟 DOM 要解决的问题:
虚拟 DOM 的运行过程:
从双缓存和 MVC 模型看虚拟 DOM:
PWA(Progressive Web App),渐进式网页应用:
PWA 采用一个缓和的渐进式策略,不是直接取代本地 APP、取代小程序,而是充分发挥 Web 的优势,渐进式缩短和本地应用或小程序之间的距离。
Web 最大的优势可以说是自由开放,因为自由开发,大家很容易对同一件事达成共识,达成共识后,一套代码就可以在各种设备上运行了,即跨平台。这是本地应用所不具备的。
可以说,PWA 是一套理念,渐进式增强 Web 的优势,并通过技术手段渐进式缩短和本地应用或者小程序之间的距离。
相对于本地应用,Web 页面缺少一些能力:
PWA 提出的解决方案是:
manifest.json
解决一级入口问题Service Worker 思路是在页面和网络之间增加一个拦截器,来缓存和拦截请求。
组件化的核心是对内高内聚,对外低耦合。对内各个元素彼此紧密结合、相互依赖,对外和其他组件联系最少且接口简单。
Web Component 的思路是提供对局部视图封装能力,可以让 DOM、CSSOM 和 JavaScript 运行在局部环境中,使得局部的 CSS 和 DOM 不会影响到全局。
Web Component (opens new window) 是一套技术的组合,涉及 Custom Element、 Shadow DOM 和 HTML Template。
<!DOCTYPE html>
<html>
<body>
<!-- 定义模板 -->
<template id="my-tpl">
<!-- 定义内部 CSS 样式 -->
<style>
p {
background: brown;
color: cornsilk;
}
div {
width: 200px;
background-color: bisque;
border: 3px solid chocolate;
border-radius: 10px;
}
</style>
<!-- 定义内部 HTML -->
<div>
<p>Hello World</p>
</div>
<!-- 定义 JavaScript 行为 -->
<script>
function sayHello() {
console.log('Hello World');
}
</script>
</template>
<script>
// 定义组件
class MyComponent extends HTMLElement {
constructor() {
super();
// 获取组件模板
const template = document.querySelector('#my-tpl').content;
// 创建 影子 DOM 节点
const shadow = this.attachShadow({ mode: 'open' });
// 将模板插入到影子 DOM 节点
shadow.appendChild(template.cloneNode(true));
}
}
// 注册组件
customElements.define('my-component', MyComponent);
</script>
<my-component></my-component>
</body>
</html>
使用 Web Component 的三个步骤:
影子 DOM 的作用:
影子 DOM 的实现:
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有