Web页面全链路性能优化指南

共 7274字,需浏览 15分钟

 ·

2022-05-19 17:58

性能优化不单指优化一个页面的打开速度,在开发环境将一个项目的启动时间缩短使开发体验更好也属于性能优化,大文件上传时为其添加分片上传、断点续传也属于性能优化。在项目开发以及用户使用的过程中,能够让任何一个链路快一点,都可以被叫做性能优化。

本文会对web页面的全链路进行完整的讲解并针对每一步找到能做的性能优化点,本文的目标是极致的性能优化。

因为针对性能优化,能做的点会特别特别的多,覆盖着整个互联网的访问流程,因此此文章的内容会比较多且杂,笔者会尽量对内容进行分类讲解。

本文的大致流程为先讲理论知识,比如如何评价一个页面的性能好与不好、如果获取性能指标,如何使用各种性能相关工具,浏览器如何获取并渲染页面。笔者认为这些都是基础,只有了解了这些基础才能开始考虑如何去优化。

接下来我们会进入性能优化环节,在这个环节我会详细讲解在页面的整个流程中,哪些地方可以做哪些优化。

目录


  • 进程与线程

  • 输入url到页面展示完整过程

    • 1.用户输入

    • 2.卸载原页面并重定向到新页面

    • 3.处理Service Worker

    • 4.网络请求

    • 5.服务端响应

    • 6.浏览器渲染详细流程

  • 浏览器处理每一帧的流程

  • Chrome Performance(性能)

    • Chrome Performance 工具的使用

    • Performance API介绍

    • 使用Performance API获取性能相关指标

  • Coverage(覆盖率)

  • Lighthouse

  • Network(网络)

    • 网络请求中的Timing(时间)

    • 网络请求的优先级

    • 网页总资源信息

    • Network配置

  • 网络优化策略

    • 减少HTTP请求数

    • 使用HTTP缓存

    • 使用 HTTP/2.0

    • 避免重定向

    • 使用 dns-prefetch

    • 使用域名分片

    • CDN

    • 压缩

    • 使用contenthash

    • 合理使用preload、prefetch

  • 浏览器渲染优化策略

    • 关键渲染路径

    • 强制同步布局问题

    • 如何减少重排与重绘

  • 静态文件优化策略

    • 图片格式

    • 图片优化

    • HTML优化

    • CSS优化

    • JS优化

    • 字体优化

  • 浏览器储存优化策略

    • Cookie

    • LocalStorage

    • SessionStorage

    • IndexDB

  • 其他优化策略

  • 使用PWA提高用户体验


浏览器渲染原理

我们需要知道浏览器是如何渲染一个页面的,我们才能知道如何对页面进行性能优化,所以这里我们对一些基础知识进行讲解

进程与线程

浏览器有多种进程,其中最主要的5种进程如下

  1. 浏览器进程 负责界面展示、用户交互、子进程管理、提供存储等
  2. 渲染进程 每个页面都有一个单独的渲染进程,用于渲染页面,包含webworker线程
  3. 网络进程 主要处理网络资源加载(HTML、CSS、JS、IMAGE、AJAX等)
  4. GPU进程 3D绘制,提高性能
  5. 插件进程 chrome插件,每个插件占用一个进程

输入url到页面展示完整过程

图1

图1

1.用户输入

用户在浏览器进程输入并按下回车健后,浏览器判断用户输入的url是否为正确的url,如果不是,则使用默认的搜索引擎将该关键字拼接成url。

2.卸载原页面并重定向到新页面

然后浏览器会将现有页面卸载掉并重定向到用户新输入的url页面,也就是图中【Process Unload Event】和【Redirect】流程。

此时浏览器会准备一个渲染进程用于渲染即将到来的页面,和一个网络进程用于发送网络请求。

3.处理Service Worker

如果当前页面注册了Service Worker那么它可以拦截当前网站所有的请求,进行判断是否需要向远程发送网络请求。也就是图中【Service Worker Init】与【Service Worker Fecth Event 】步骤

如果不需要发送网络请求,则取本地文件。如果需要则进行下一步。

4.网络请求

OSI网络七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层

在实际应用中物理层、数据链路层被统称为物理层,会话层、表示层、应用层被统称为应用层,所以实际使用时通常分为4个层级

【物理层】>【网络层(IP)】>【传输层(TCP/UDP)】>【应用层(HTTP)】

也就是图中【HTTP Cache】、【DNS】、【TCP】、【Request】、【Response】步骤

图2

图2

浏览器会拿着url通过网络进程进行如下步骤

  1. 根据url查询本地是否已经有强制缓存,如果有则判断缓存是否过期,如果没过期则直接返回缓存内容,也就是图1中【HTTP Cache】步骤

  2. 如果没有强制缓存或者缓存已过期,则将该请求加入队列进行排队准备发送网络请求,也就是图2中【正在排队】,然后进入DNS解析阶段,也就是图1中【DNS】以及图2中的【DNS查找】,DNS根据域名解析出对应的IP地址。(DNS基于UDP)。

  3. 然后使用IP寻址找到对方,然后根据IP地址+端口号创建一个TCP连接(三次握手),也就是图1中【TCP】以及图2中的【初始连接】创建完成后利用TCP连接来传输数据。(TCP会将数据拆分为多个数据包,进行有序传输,如果丢包会重发,TCP的特点是可靠、有序)

  4. 判断当前协议是否为https,如果为https,则进行SSL协商,将数据进行加密,如果为http协议则不进行加密(明文传输),也就是图2中的【SSL】。

  5. 开始发送http请求(请求行/请求头/请求体),也就是图1中【Request】以及图2中的【已发送请求】。HTTP协议有多个版本,目前使用最多的版本为HTTP/1.1,HTTP/1.1发送完成后默认不会断开。keep-alive 默认打开,为了下次传输数据时复用上次创建的连接。每个域名最多同时建立6个TCP连接,所以同一时间最多发生6个请求。

    HTTP协议的各个版本特性如下:

    • HTTP/0.9 没有请求头和响应头,不区分传输的内容类型,因为当时只传输HTML。
    • HTTP/1.0 提供了请求头和响应头,可以传输不同类型的内容数据。根据请求响应头的不同来处理不同的资源,HTTP1.0每次发完请求都会断开TCP连接。有新的请求时再次创建TCP连接。
    • HTTP/1.1 默认开启了 keep-alive ,它能够让一个TCP连接中传输多个HTTP请求,也叫链路复用。但一个TCP连接同一时间只能发送一个HTTP请求,为了不阻塞多个请求,Chrome允许创建6个TCP连接,所以在HTTP/1.1中,最多能够同时发送6个网络请求。
    • HTTP/2.0 HTTP/2.0使用同一个TCP连接来发送数据,他把多个请求通过二进制分贞层实现了分贞,然后把数据传输给服务器。也叫多路复用,多个请求复用同一个TCP连接。HTTP/2.0会将所有以:开头的请求头做一个映射表,然后使用hpack进行压缩,使用这种方式会使请求头更小。服务器可主动推送数据给客户端。
    • HTTP/3.0 使用UDP实现,在UDP上一层加入一层QUIC协议,解决了TCP协议中的队头阻塞问题。
  6. 服务器收到数据后解析HTTP请求(请求行/请求头/请求体),处理完成后生成状态码和HTTP响应(响应行/响应头/响应体)后返回给客户端,也就是图2的【等待中】在做的事情。

  7. 客户端接收到HTTP响应后根据状态码进行对应的处理,如果状态码为304则直接代表协商缓存生效,直接取本地的缓存文件。如果不是则下载内容。也就是图1中【Response】以及图2中的【下载内容】步骤。

5.服务端响应

4.网络请求6步中,服务器收到HTTP请求后需要根据请求信息来进行解析,并返回给客户端想要的数据,这也就服务端响应。

服务端可以响应并返回给客户端很多种类型的资源,这里主要介绍html类型

目前前端处理服务端响应html请求主要分为SSR服务端渲染与CSR客户端渲染,CSR就是返回一个空的HTML模版,然后浏览器加载js后通过js动态渲染页面。SSR是服务端在接受到请求时事先在服务端渲染好html返回给客户端后,客户端再进行客户端激活。

在打开一个站点的首屏页的完整链路中,使用SSR服务端渲染时的速度要远大于CSR客户端渲染,并且SSR对SEO友好。所以对于首屏加载速度比较敏感或者需要优化SEO的站点来说,使用SSR是更好的选择。

6.浏览器渲染详细流程

浏览器渲染详细流程主要在4.网络请求中的地7步。浏览器下载完html内容后进行解析何渲染页面的流程。

渲染流程分为4种情况,

  1. HTML中无任何CSS相关标签
  2. CSS相关标签在HTML最顶部,且在解析到内容标签(
    )时已经解析完CSS相关标签
  3. CSS相关标签在HTML最顶部,但在解析到内容标签(
    )时CSS相关标签尚未解析完
  4. CSS相关标签在HTML最底部

下面的流程是对上图的文字版解析。读者可将以上4种情况分别带入到如下的渲染流程中走一遍。就能理解浏览器的完整渲染过程了。

【HTML】

浏览器收到html资源后先预扫描


我们遵循读写分离的原则,将读取位置操作放到函数外,我们可以发现就算循环插入10个dom节点,也只需要执行一次【计算样式】和【重排】。

如何减少重排与重绘

  1. 脱离文档流(绝对定位、固定定位),脱离文档流的元素进行重排不会影响到其他元素。
  2. 图片渲染时增加宽高属性,宽高固定后,图片不会根据内容动态改变高度,便不会触发重排。
  3. 尽量用CSS3动画,CSS3动画能最大程度减少重排与重绘。
  4. 使用will-change: transform;将元素独立为一个单独的图层。(定位、透明、transform、clip都会产生独立图层)

静态文件优化策略

图片格式

jpeg

适合色彩丰富的图、Banner图。不适合:图形文字、图标、不支持透明度。

png

适合纯色、透明、图标,支持纯透明和半透明。不适合色彩丰富图片,因为无损储存会导致储存体积大于jpeg

gif

适合动画、可以动的图标。支持纯透明但不支持半透明,不适合色彩丰富的图片。

埋点信息通常也会使用gif发送,因为1x1的gif图发送的网络请求比普通的get请求要小一些。

webp

支持纯透明和半透明,可以保证图片质量和较小的体积,适合Chrome和移动端浏览器。不适合其他浏览器。

svg

矢量格式,大小非常小,但渲染成本过高,适合小且色彩单一的图标。

图片优化

  • 减少图片资源的尺寸和大小,节约用户流量
  • 设置alt="xxx"属性,图像无法显示时会显示alt内容
  • 图片懒加载, loading="lazy"为原生,建议使用IntersectionObserver自己做懒加载
  • 不同环境加载不同尺寸和像素的图片srcsetsizes的使用。
  • 采用渐进式加载 先加载占位图,然后加载模糊小图,最后加载真正清晰的图
  • 使用Base64URL 减少图片请求数
  • 采用雪碧图合并图片,减少请求数。

HTML优化

  • 语义化HTML,代码简洁清晰,利于SEO,便于开发维护。
  • 减少HTML嵌套关系,减少DOM节点数量。
  • 提前声明字符编码,让浏览器快速确定如何渲染网页内容
  • 删除多余空格、空行、注释、无用属性
  • 减少iframe,子iframe会阻塞父级的onload事件。可以使用js动态给iframe赋值,就能解决这个问题。
  • 避免table布局

CSS优化

  • 减少伪类选择器,减少选择器层数、减少通配符选择器、减少正则选择器
  • 避免css表达式background-color: expression(...)
  • 删除空格、空行、注释、减少无意义的单位、css压缩
  • css外链,能走缓存
  • 添加媒体字段,只加载有效的css文件
<link rel="stylesheet" href="./small.css" media="screen and (max-width:600px)" />
<link rel="stylesheet" href="./big.css" media="screen and (min-width:601px)"/>
  • 使用css contain属性,能控制对应元素是否根据子集元素的改变进行重排
  • 减少@import使用,因为它使用串行加载

JS优化

  • 通过script的async、defer属性异步加载,不阻塞DOM渲染
  • 减少DOM操作,缓存访问过的元素。
  • 不直接操作真实DOM,可以先修改,然后一次性应用到DOM上。(虚拟DOM、DOM碎片节点)
  • 使用webworker解决复杂运算,避免复杂运算阻塞主线程,webworker线程位于渲染进程
  • 图片懒加载,使用IntersectionObserver实现
html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      img {
        height200px;
        display: block;
      }
    
style>
    <title>Documenttitle>
  head>
  <body>
    <img src="./loading.gif" src="./01.jpg" />
    <img src="./loading.gif" src="./02.jpg" />
    <img src="./loading.gif" src="./03.jpg" />
    <img src="./loading.gif" src="./04.jpg" />
    <img src="./loading.gif" src="./05.jpg" />
    <img src="./loading.gif" src="./06.jpg" />
    <img src="./loading.gif" src="./07.jpg" />
    <img src="./loading.gif" src="./08.jpg" />
    <img src="./loading.gif" src="./09.jpg" />
    <img src="./loading.gif" src="./10.jpg" />

    <script>
      const intersectionObserver = new IntersectionObserver((changes) => {
        changes.forEach((item, index) => {
          if (item.intersectionRatio > 0) {
            intersectionObserver.unobserve(item.target)
            item.target.src = item.target.dataset.src
          }
        })
      });

      const domImgList = document.querySelectorAll("img");
      domImgList.forEach((domImg) => intersectionObserver.observe(domImg));
    
script>
  body>
html>

  • 虚拟滚动
  • 使用requestAnimationFrame来做动画,使用requestIdleCallback来进行空闲时的任务处理
  • 尽量避免使用eval,性能差。
  • 使用事件委托,能减少事件绑定个数。事件越多性能越差。
  • 尽量使用canvas、css3动画。
  • 通过chrome覆盖率(Coverage)工具排查代码中未使用过的代码并将其删除
  • 通过chrome性能(Performance)工具查看每个函数的执行性能并优化

字体优化

FOUT(Flash of Unstyled Text)等待一段时间,如果没加载完成,先显示默认。加载 后再进行切换。

FOIT(F1ash of Invisib1e Text) 字体加载完毕后显示,加载超时降级系统字体(白 屏

html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    @font-face {
      font-family'hagan';
      srcurl('./font.ttc');
      font-display: swap;
      /* b1ock 35 内不显示,如果没加载完毕用默认的 */
      /* swap 显示老字体 在替换*/
      /* fa11back 缩短不显示时间,如果没加载完毕用默认的,和b1ock类似*
      /* optional 替换可能用字体 可能不替换*/

    }
    article {
      font-family: hagan;
    }
  
style>
  <title>Documenttitle>
head>
<body>
  <article>ABC abcarticle>  
body>
html>

浏览器储存优化策略

Cookie

cookie在过期之前一直有效,最大储存大小为4k,限制字段个数,不适合大量的数据储存,每次请求会携带cookie,主要用来做身份校验。

优化方式:

  1. 需要合理设置cookie有效期
  2. 根据不同子域划分cookie来减少cookie传输
  3. 静态资源域名和cookie域名采用不同域名,避免静态资源请求携带cookie

LocalStorage

Chrome下最多储存5M,除非手动清除,否则一直存在。可以利用localStorage储存静态资源。比如储存网页的.js.css,这样会使页面打开速度非常快。例如 https://m.baidu.com

/index.js

const name = 'hagan'
function showName ({
  console.log(name)
}
showName()

/index.html

html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Documenttitle>
head>
<body>
  <script src="https://lib.baomitu.com/axios/0.26.1/axios.js">script>
  <script>
    cacheFile('/index.js')

    async function cacheFile (url{
      const fileContent = localStorage.getItem(url)
      if (fileContent) {
        eval(fileContent)
      } else {
        const { data } = await axios.get(url)
        eval(data)
        localStorage.setItem(url, data)
      }
    }
  
script>
body>
html>

SessionStorage

会话级别储存,可用于页面间的传值

IndexDB

浏览器的本地数据库,大小几乎无上限

其他优化策略

  • 关键资源个数越多,首次页面加载时间就会越长
  • 关键资源的大小,内容越小下载时间越短。
  • 优化白屏,合理使用内联css、js
  • 预渲染,打包时进行预渲染,生成静态HTML文件,用户访问时直接返回静态HTML。
  • 服务端渲染同构,加速首屏速度(耗费服务端资源),有利于SEO优化。首屏使用服务端渲染,后续交互使用客户端渲染。

使用PWA提高用户体验

webapp用户体验差的一大原因是不能离线访问。用户粘性低的一大原因是无法保存入口,PWA就是为了解决webapp的用户体验问题而诞生的。使用PWA能令站点拥有快速、可靠、安全等特性。

  1. Web App Manifest 将网站添加到电脑桌面、手机桌面,类似Native的体验。
  2. Service Worker 配合Cache API,能做到离线缓存各种内容。
  3. Push API 配合 Notification API,能做到类似Native的消息推送与实时提醒。
  4. App Shell 配合 App Skeleton,能做App壳与骨架屏

参考资料

  • https://www.w3.org/TR/navigation-timing-2/
浏览 59
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报