如何系统的优化前端性能

前言

  • 从用户的角度而言,当打开一个网页,往往关心的是从输入完网页地址后到最后展现完整页面这个过程需要的时间,这个时间越短,用户体验越好。所以作为网页的开发者,就从输入url到页面渲染呈现这个过程中去提升网页的性能。
  • 所以输入URL后发生了什么呢?在浏览器中输入url会经历域名解析、建立TCP连接、发送http请求、资源解析等步骤。
  • 本文主要从网页渲染过程、网页交互以及应用代码优化三个角度对性能优化做一个小结。
  • 这里直接说影响因素
    • http的请求数量,tcp协议的问题
      • 关键资源的数量: 可能阻止网页首次渲染的资源。
      • 关键路径长度: 获取所有关键资源所需的往返次数或总时间。
      • 关键字节: 实现网页首次渲染所需的总字节数,等同于所有关键资源传送文件大小的总和。
    • 优化 DOM
    • 浏览器的渲染机制,如果遇到Javascript文件,HTML文件会挂起渲染的进程,等待JavaScript文件加载完毕后,再继续进行渲染。
    • 用户视觉效果

页面的三个阶段

  • 通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段。

    • 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本。
    • 交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本。
    • 关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作。
  • 首先想要优化性能必须知道什么会影响性能,那么我们就要关注加载和交互阶段,想了解更多需要了解浏览器的运行机制,http协议,以及用户角度考虑问题。你应该已经知道了并非所有的资源都会阻塞页面的首次绘制,比如图片、音频、视频等文件就不会阻塞页面的首次渲染;而 JavaScript、首次请求的 HTML 资源文件、CSS 文件是会阻塞首次渲染的,因为在构建 DOM 的过程中需要 HTML 和 JavaScript 文件,在构造渲染树的过程中需要用到 CSS 文件。

加载阶段(总的优化原则就是减少关键资源个数,降低关键资源大小,降低关键资源的 RTT 次数):

  • 如何减少关键资源的个数? 一种方式是可以将 JavaScript 和 CSS 改成内联的形式,比如 JavaScript 和 CSS,若都改成内联模式,那么关键资源的个数就由 3 个减少到了 1 个。另一种方式,如果 JavaScript 代码没有 DOM 或者 CSSOM 的操作,则可以改成 async 或者 defer 属性;同样对于 CSS,如果不是在构建页面之前加载的,则可以添加媒体取消阻止显现的标志。当 JavaScript 标签加上了 async 或者 defer、CSSlink 属性之前加上了取消阻止显现的标志后,它们就变成了非关键资源了。
  • 如何减少关键资源的大小? 可以压缩 CSS 和 JavaScript 资源,移除 HTML、CSS、JavaScript 文件中一些注释内容,也可以通过前面讲的取消 CSS 或者 JavaScript 中关键资源的方式。
  • 如何减少关键资源 RTT 的次数? 可以通过减少关键资源的个数和减少关键资源的大小搭配来实现。除此之外,还可以使用 CDN 来减少每次 RTT 时长。

交互阶段

交互阶段其实就是渲染进程渲染帧的速度,因为在交互阶段,帧的渲染速度决定了交互的流畅度。因此讨论页面优化实际上就是讨论渲染引擎是如何渲染帧的,否则就无法优化帧率。大部分情况下,生成一个新的帧都是由 JavaScript 通过修改 DOM 或者 CSSOM 来触发的。还有另外一部分帧是由 CSS 来触发的。

  • 回流:如果在计算样式阶段发现有布局信息的修改,那么就会触发重排操作,然后触发后续渲染流水线的一系列操作,这个代价是非常大的。
  • 重绘:同样如果在计算样式阶段没有发现有布局信息的修改,只是修改了颜色一类的信息,那么就不会涉及到布局相关的调整,所以可以跳过布局阶段,直接进入绘制阶段,这个过程叫重绘。
  • 减少 JavaScript 脚本执行时间
    • 一种是将一次执行的函数分解为多个任务,使得每次的执行时间不要过久。
    • 另一种是采用 Web Workers。你可以把 Web Workers 当作主线程之外的一个线程,在 Web Workers 中是可以执行 JavaScript 脚本的,不过 Web Workers 中没有 DOM、CSSOM 环境,这意味着在 Web Workers 中是无法通过 JavaScript 来访问 DOM 的,所以我们可以把一些和 DOM 操作无关且耗时的任务放到 Web Workers 中去执行。
  • 避免强制同步布局(所谓强制同步布局,是指 JavaScript 强制将计算样式和布局操作提前到当前的任务中)
/** 正常布局操作 */
<html>
<body>
    <div id="mian_div">
        <li id="time_li">time</li>
        <li>geekbang</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("time.geekbang")
            new_node.appendChild(textnode);
            document.getElementById("mian_div").appendChild(new_node);
        }
    </script>
</body>
</html>
// 强制同步布局
function foo() {
    let main_div = document.getElementById("mian_div")
    let new_node = document.createElement("li")
    let textnode = document.createTextNode("time.geekbang")
    new_node.appendChild(textnode);
    document.getElementById("mian_div").appendChild(new_node);
    //由于要获取到offsetHeight,
    //但是此时的offsetHeight还是老的数据,
    //所以需要立即执行布局操作
    console.log(main_div.offsetHeight)
}
  • 避免布局抖动(所谓布局抖动,是指在一次 JavaScript 执行过程中,多次执行强制布局和抖动操作)
// 我们在一个 for 循环语句里面不断读取属性值,每次读取属性值之前都要进行计算样式和布局。
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("time.geekbang")
        new_node.appendChild(textnode);
        new_node.offsetHeight = time_li.offsetHeight;
        document.getElementById("mian_div").appendChild(new_node);
    }
}
  • 合理利用 CSS 合成动画
    • 合成动画是直接在合成线程上执行的,这和在主线程上执行的布局、绘制等操作不同,如果主线程被 JavaScript 或者一些布局任务占用,CSS 动画依然能继续执行。所以要尽量利用好 CSS 合成动画,如果能让 CSS 处理动画,就尽量交给 CSS 来操作。另外,如果能提前知道对某个元素执行动画操作,那就最好将其标记为 will-change,这是告诉渲染引擎需要将该元素单独生成一个图层。
    • 简单的说就是利用合成动画替代js动画
  • 避免频繁的垃圾回收
    • 我们知道 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。所以要尽量避免产生那些临时垃圾数据。那该怎么做呢?可以尽可能优化储存结构,尽可能避免小颗粒对象的产生。

页面加载及渲染过程优化

  • webpack按需打包
  • 善用缓存,不重复加载相同的资源(了解强缓存和协商缓存)
  • 首屏优化 ssr服务端渲染,路由懒加载和图片懒加载
  • 使用空值判断进行加载中的动画渲染
  • 使用try catch 进行错误处理404页面 避免报错
  • CDN 缓存原理
    • 如果用户访问的网站部署了 CDN,过程是这样的:
    • 浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。
    • 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到全局负载均衡系统(GSLB)的 IP 地址。
    • 本地 DNS 再向 GSLB 发出请求,GSLB 的主要功能是根据本地 DNS 的 IP 地址判断用户的位置,筛选出距离用户较近的本地+ + 负载均衡系统(SLB),并将该 SLB 的 IP 地址作为结果返回给本地 DNS。
    • 本地 DNS 将 SLB 的 IP 地址发回给浏览器,浏览器向 SLB 发出请求。
    • SLB 根据浏览器请求的资源和地址,选出最优的缓存服务器发回给浏览器。
    • 浏览器再根据 SLB 发回的地址重定向到缓存服务器。
    • 如果缓存服务器有浏览器需要的资源,就将资源发回给浏览器。如果没有,就向源服务器请求资源,再发给浏览器并缓存在本地。
      在这里插入图片描述

渲染完成后的页面交互优化

  • 在多次请求和操作的地方进行节流和防抖
  • 在不同的框架使用时选择合适的传值方式 例如在react的类组件进行传值时,需要进行this绑定,如何进行选择的问题,在constructor里使用bind进行绑定性能最好。以及shouldComponentUpdate等生命周期的合理使用,diff算法的key值使用,immutable的使用等
  • 在用户网络较差的环境下不停的进行数据请求,这时候可以在页面销毁时取消数据请求。

小结

性能优化涉及的知识面很广,并且也比较深,如果想要弄懂这里面的原理以及熟练使用还需要大量的知识积累,暂时想到的只有这么多,还有一些图片和css的优化就不提了,欢迎大家补充,互相进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MaxLoongLvs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值