浏览器原理 24 # 页面性能:如何系统地优化页面?

简介: 浏览器原理 24 # 页面性能:如何系统地优化页面?

说明

浏览器工作原理与实践专栏学习笔记



前言

通常一个页面有三个阶段:


   加载阶段:是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本。


   交互阶段:主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本。


   关闭阶段:主要是用户发出关闭指令后页面所做的一些清理操作。


下面重点看一下加载阶段和交互阶段的优化。



加载阶段


加载阶段渲染流水线

20210524111158153.png


对关键资源进行优化

能阻塞网页首次渲染的资源称为关键资源

JavaScript、首次请求的 HTML 资源文件、CSS 文件是会阻塞首次渲染的,而图片、音频、视频等文件就不会阻塞页面的首次渲染


影响页面首次渲染的核心因素:


关键资源个数


关键资源个数越多,首次页面的加载时间就会越长。


如何减少关键资源的个数?


   将 JavaScript 和 CSS 改成内联的形式,比如上图的 JavaScript 和 CSS,若都改成内联模式,那么关键资源的个数就由 3 个减少到了 1 个。


   如果 JavaScript 代码没有 DOM 或者 CSSOM 的操作,则可以改成 async 或者 defer 属性;同样对于 CSS,如果不是在构建页面之前加载的,则可以添加媒体取消阻止显现的标志。


   当 JavaScript 标签加上了 async 或者 defer、CSSlink 属性之前加上了取消阻止显现的标志后,它们就变成了非关键资源了。


关键资源大小


   通常情况下,所有关键资源的内容越小,其整个资源的下载时间也就越短,那么阻塞渲染的时间也就越短。


如何减少关键资源的大小?


   可以压缩 CSS 和 JavaScript 资源,移除 HTML、CSS、JavaScript 文件中一些注释内容,也可以通过前面讲的取消 CSS 或者 JavaScript 中关键资源的方式。


请求关键资源需要多少个 RTT(Round Trip Time)


   RTT 是网络中一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。通常 1 个 HTTP 的数据包在 14KB 左右,所以 1 个 0.1M 的页面就需要拆分成 8 个包来传输了,也就是说需要 8 个 RTT。


   注意:由于渲染引擎有一个预解析的线程,在接收到 HTML 数据之后,预解析线程会快速扫描 HTML 数据中的关键资源,一旦扫描到了,会立马发起请求,可以认为 JavaScript 和 CSS 是同时发起请求的,所以它们的请求是重叠的,那么计算它们的 RTT 时,只需要计算体积最大的那个数据就可以了。例如上面渲染图中最大的是 CSS 文件(9KB),所以我们就按照 9KB 来计算,同样由于 9KB 小于 14KB(1 个 RTT),所以 JavaScript 和 CSS 资源也就可以算成 1 个 RTT。也就是说,图中关键资源请求共花费了 2 个 RTT。


如何减少关键资源 RTT 的次数?


   可以通过减少关键资源的个数和减少关键资源的大小搭配来实现。除此之外,还可以使用 CDN 来减少每次 RTT 时长。


总的优化原则:减少关键资源个数,降低关键资源大小,降低关键资源的 RTT 次数。



交互阶段

交互阶段的优化,其实就是在谈渲染进程渲染帧的速度,因为在交互阶段,帧的渲染速度决定了交互的流畅度。


交互阶段渲染流水线

20210524161829750.png


关于如何生成一帧图像:这个就不多讲了,可以去参考上一篇文章浏览器原理 23 # 分层和合成机制:为什么CSS动画比JavaScript高效?



如何优化渲染帧的速度


1. 减少 JavaScript 脚本执行时间


   一种是将一次执行的函数分解为多个任务,使得每次的执行时间不要过久。


   另一种是采用 Web Workers。


   在 Web Workers 中是可以执行 JavaScript 脚本的,不过 Web Workers 中没有 DOM、CSSOM 环境,这意味着在 Web Workers 中是无法通过 JavaScript 来访问 DOM 的,可以把一些和 DOM 操作无关且耗时的任务放到 Web Workers 中去执行。


拓展:


   JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。————来自阮一峰网络日志:Web Worker 使用教程


2. 避免强制同步布局


讲这个之前,我们先看正常情况下的布局操作。


正常情况下的布局操作:


例子:

<!DOCTYPE html>
<html>
<body>
    <div id="mian_div">
        <li id="time_li">time</li>
        <li>kxm</li>
    </div>
    <p id="demo">强制布局demo</p>
    <button onclick="foo()">添加新元素</button>
    <script>
        function foo() {
            let main_div = document.getElementById("mian_div")      
            let new_node = document.createElement("li")
            let textnode = document.createTextNode("kxm-test")
            new_node.appendChild(textnode);
            document.getElementById("mian_div").appendChild(new_node);
        }
    </script>
</body>
</html>


Performance 记录添加元素的执行过程:


20210524180001745.png


我自己测试了一下:从图中可以看出来,执行 JavaScript 添加元素是在一个任务中执行的,重新计算样式布局是在另外一个任务中执行,这就是正常情况下的布局操作。

20210524180056437.png



强制同步布局,是指 JavaScript 强制将计算样式和布局操作提前到当前的任务中。

例子:在上面代码的基础上改造一下

function foo() {
    let main_div = document.getElementById("mian_div")      
    let new_node = document.createElement("li")
    let textnode = document.createTextNode("kxm-test")
    new_node.appendChild(textnode);
    document.getElementById("mian_div").appendChild(new_node);
    //由于要获取到offsetHeight,
    //但是此时的offsetHeight还是老的数据,
    //所以需要立即执行布局操作
    console.log(main_div.offsetHeight)
}


触发强制同步布局 Performance 图:


20210524181700990.png


我自己测试了一下:从图中可以看出来,计算样式和布局都是在当前脚本执行过程中触发的,代码里要获取到 main_div  的高度,就需要重新布局,所以这里在获取到 main_div 的高度之前,JavaScript 还需要强制让渲染引擎默认执行一次布局操作,这种就是强制同步布局

20210524181933998.png


如何避免强制同步布局

比如上面代码,可以调整策略,在修改 DOM 之前查询相关值。


3. 避免布局抖动


所谓布局抖动,是指在一次 JavaScript 执行过程中,多次执行强制布局和抖动操作。


我们继续在上面的例子的基础上修改:

function foo() {
    let time_li = document.getElementById("time_li")
    for (let i = 0; i < 100; i++) {
        let main_div = document.getElementById("mian_div")
        let new_node = document.createElement("li")
        let textnode = document.createTextNode("kxm-test")
        new_node.appendChild(textnode);
        new_node.offsetHeight = time_li.offsetHeight;
        document.getElementById("mian_div").appendChild(new_node);
    }
}



Performance 中关于布局抖动的表现:

20210524201751813.png


我自己测试了一下:从图中可以看出,在 foo 函数内部重复执行计算样式和布局,这会大大影响当前函数的执行效率。


202105242017189.png


避免方式和强制同步布局一样,都是尽量不要在修改 DOM 结构时再去查询一些相关值。


4. 合理利用 CSS 合成动画

合成动画是直接在合成线程上执行的,这和在主线程上执行的布局、绘制等操作不同,如果主线程被 JavaScript 或者一些布局任务占用,CSS 动画依然能继续执行。


比如:如果能提前知道对某个元素执行动画操作,那就最好将其标记为 will-change,这是告诉渲染引擎需要将该元素单独生成一个图层。



5. 避免频繁的垃圾回收

JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。


怎么避免?


尽可能优化储存结构,尽可能避免小颗粒对象的产生。






目录
相关文章
|
5天前
|
缓存 监控 前端开发
基于时间缓存优化浏览器轮询阻塞问题
基于时间缓存优化浏览器轮询阻塞问题
16 0
|
2月前
|
缓存 JavaScript
vue阻止浏览器刷新和关闭页面提示
使用场景:在使用vuex进行缓存管理时,页面的缓存会随着页面关闭而消失,如果缓存动作仍在进行中,关闭页面会导致数据丢失,此时需要阻止页面关闭
67 3
|
3月前
|
数据采集 Web App开发 JSON
浏览器插件:WebScraper基本用法和抓取页面内容(不会编程也能爬取数据)
本文以百度为实战案例演示使用WebScraper插件抓取页面内容保存到文件中。以及WebScraper用法【2月更文挑战第1天】
135 2
浏览器插件:WebScraper基本用法和抓取页面内容(不会编程也能爬取数据)
|
3月前
|
Web App开发 小程序 前端开发
【产品上新】小程序新内核来了!提升安卓浏览器性能,支持WebRTC
【产品上新】小程序新内核来了!提升安卓浏览器性能,支持WebRTC
41 0
|
3月前
|
存储 缓存 前端开发
浏览器缓存工作原理是什么?
浏览器缓存工作原理是什么?
|
23天前
【超实用】Angular如何修改当前页面网页浏览器url后面?param1=xxx&param2=xxx参数(多用于通过浏览器地址参数保存用户当前操作状态的需求),实现监听url路由切换、状态变化。
【超实用】Angular如何修改当前页面网页浏览器url后面?param1=xxx&param2=xxx参数(多用于通过浏览器地址参数保存用户当前操作状态的需求),实现监听url路由切换、状态变化。
|
24天前
|
搜索推荐 前端开发 UED
html页面实现自动适应手机浏览器(一行代码搞定)
html页面实现自动适应手机浏览器(一行代码搞定)
21 0
|
2月前
|
Web App开发 缓存 网络协议
|
3月前
|
存储 安全 前端开发
浏览器跨窗口通信:原理与实践
浏览器跨窗口通信:原理与实践
47 0
|
3月前
|
消息中间件 JavaScript 前端开发
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)

热门文章

最新文章