Web页面全链路性能优化指南
跟着动画来学习TCP三次握手和四次挥手

浏览器渲染原理

进程与线程

浏览器有多种进程,其中最重要的5种进程如下
image.png

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

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

    640.svg

    Chrome 性能优化相关工具

    image.png

Coverage(覆盖率)

Lighthouse

Network(网络)

image.png

  • 正在排队:网络请求队列的排队时间
  • 已停止:阻塞住用于处理其他事情的时间
  • DNS查找:用于DNS解析IP地址的时间
  • 初始连接:创建TCP连接时间
  • SSL:用于SSL协商的时间
  • 已发送请求:用于发送请求的时间
  • 等待中:请求发出至接收响应的时间也可以理解为服务端处理请求的时间
  • 下载内容:下载响应的时间

    网络请求的优先级

    浏览器会根据资源的类型决定优先请求哪些资源,优先级高的请求能够优先被加载。
    不同资源类型的优先级排序如下

  • 最高:html、style

  • 高:font、fetch、script
  • 低:image、track

    网络页性能优化

    网络优化策略

    减少HTTP请求数
    合并JS、合并CSS、合理内嵌JS和CSS、使用雪碧图
    使用HTTP缓存
    使用协商缓存可以减少数据传输,当不需要更新数据时可通知客户端直接使用本地缓存。
    使用强制缓存可以不走网络请求,直接走本地缓存数据来加载资源。
    使用HTTP/2.0
    HTTP/2.0会将所有以:开头的请求头做一个映射表,然后使用hpack进行压缩,使用这种方式会使请求头更小。
    服务器可主动推送数据给客户端。
    HTTP/2.0使用同一个TCP连接来发送数据,他把多个请求通过二进制分贞层实现了分贞,然后把数据传输给服务器。也叫多路复用,多个请求复用同一个TCP连接。
    避免重定向
    301、302 重定向会降低响应速度
    使用dns-prefetch
    DNS请求虽然占用的带宽较少,但会有很高的延迟,由其在移动端网络会更加明显。
    使用dns-prefetch可以对网站中使用到的域名提前进行解析。提高资源加载速度。
    通过dns预解析技术可以很好的降低延迟,在访问以图片为主的移动端网站时,使用DNS预解析的情意中下页面加载时间可以减少5%。
    使用域名分片
    在HTTP/1.1中,一个域名同时最多创建6个TCP连接,将资源放在多个域名下可提高请求的并发数
    CDN
    静态资源全上CDN,CDN能非常有效的加快网站静态资源的访问速度。
    压缩
    gzip压缩、html压缩、js压缩、css压缩、图片压缩
    使用contenthash
    contenthash可以根据文件内容在文件名中加hash,可用于浏览器缓存文件,当文件没有改变时便直接取本地缓存数据
    合理使用 preload prefetch
    preload预加载、prefetch空闲时间加载
    两者都不会阻塞onload事件,prefetch 会在页面空闲时候再进行加载,是提前预加载之后可能要用到的资源,不一定是当前页面使用的,preload 预加载的是当前页面的资源。

    浏览器渲染优化策略

    关键渲染路径

    当通过JS或者其他任意方式修改DOM后,浏览器会进入如下流程
    【JS通过API修改DOM】>【计算样式】>【布局(重排)】>【绘制(重绘)】>【合成】
    Reflow 重排:重排在Chrome Performance中叫做布局,通常添加或删除元素、修改元素大小、移动元素位置、获取位置信息都会触发页面的重排,因为重排可能会改变元素的大小位置等信息,这样的改变会影响到页面大量其它元素的大小位置信息,会耗费掉大量的性能,所以在实际应用中我们应该尽可能的减少重排
    Repaint 重绘:重绘在Chrome Performance中叫做绘制,通常样式改变但没有影响位置时会触发重绘操作,重绘性能还好,但我们也需要尽量减少重绘,如果需要做一些动画,我们尽量使用CSS3动画,CSS3动画只需要在初始化时绘制一次,之后的动画都不会触发重绘操作。

    强制同步布局问题

    在同一个函数内,修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能

    强制同步布局会使js强制将【计算样式】和【布局(重排)】操作提前到当前函数任务中,这样会导致每次运行时执行一次【计算样式】和【重排】,这样一定会影响页面渲染性能,而正常情况下【计算样式】和【重排】操作会在函数结束后统一执行。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <article id="article"></article>
  11. <script>
  12. const domArticle = document.querySelector('#article')
  13. // const { offsetTop } = domArticle
  14. function reflow () {
  15. const domH1 = document.createElement('h1')
  16. domH1.innerHTML = 'h1'
  17. domArticle.appendChild(domH1)
  18. /**
  19. * 强制同步布局
  20. * 在修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能
  21. * 解决办法是采用读写分离的原则,同一个函数内只读、只写
  22. */
  23. const { offsetTop } = domArticle
  24. console.log(offsetTop)
  25. }
  26. window.onload = () => {
  27. for (let i = 0; i < 10; i++) {
  28. reflow()
  29. }
  30. }
  31. </script>
  32. </body>
  33. </html>

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

    静态文件优化策略

    图片格式

    jpeg:适合色彩丰富的图、Banner图。不适合:图形文字、图标、不支持透明度。
    png:适合纯色、透明、图标,支持纯透明和半透明。不适合色彩丰富图片,因为无损储存会导致储存体积大于jpeg
    gif:适合动画、可以动的图标。支持纯透明但不支持半透明,不适合色彩丰富的图片。
    埋点信息通常也会使用gif发送,因为1x1的gif图发送的网络请求比普通的get请求要小一些。
    webp:支持纯透明和半透明,可以保证图片质量和较小的体积,适合Chrome和移动端浏览器。不适合其他浏览器。
    svg:矢量格式,大小非常小,但渲染成本过高,适合小且色彩单一的图标。

    图片优化

  • 减少图片资源的尺寸和大小,节约用户流量

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

    HTML优化

  • 语义化HTML,代码简洁清晰,利于SEO,便于开发维护。

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

    CSS优化
  • 减少伪类选择器,减少选择器层数、减少通配符选择器、减少正则选择器

  • 避免css表达式background-color: expression(…)
  • 删除空格、空行、注释、减少无意义的单位、css压缩
  • css外链,能走缓存
  • 添加媒体字段,只加载有效的css文件

    1. <link rel="stylesheet" href="./small.css" media="screen and (max-width:600px)" />
    2. <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实现
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <style>
  8. img {
  9. height: 200px;
  10. display: block;
  11. }
  12. </style>
  13. <title>Document</title>
  14. </head>
  15. <body>
  16. <img src="./loading.gif" data-src="./01.jpg" />
  17. <img src="./loading.gif" data-src="./02.jpg" />
  18. <img src="./loading.gif" data-src="./03.jpg" />
  19. <img src="./loading.gif" data-src="./04.jpg" />
  20. <img src="./loading.gif" data-src="./05.jpg" />
  21. <img src="./loading.gif" data-src="./06.jpg" />
  22. <img src="./loading.gif" data-src="./07.jpg" />
  23. <img src="./loading.gif" data-src="./08.jpg" />
  24. <img src="./loading.gif" data-src="./09.jpg" />
  25. <img src="./loading.gif" data-src="./10.jpg" />
  26. <script>
  27. const intersectionObserver = new IntersectionObserver((changes) => {
  28. changes.forEach((item, index) => {
  29. if (item.intersectionRatio > 0) {
  30. intersectionObserver.unobserve(item.target)
  31. item.target.src = item.target.dataset.src
  32. }
  33. })
  34. });
  35. const domImgList = document.querySelectorAll("img");
  36. domImgList.forEach((domImg) => intersectionObserver.observe(domImg));
  37. </script>
  38. </body>
  39. </html>
  • 虚拟滚动
  • 使用requestAnimationFrame来做动画,使用requestIdleCallback来进行空闲时的任务处理
  • 尽量避免使用eval,性能差。
  • 使用事件委托,能减少事件绑定个数。事件越多性能越差。
  • 尽量使用canvas、css3动画。
  • 通过chrome覆盖率(Coverage)工具排查代码中未使用过的代码并将其删除
  • 通过chrome性能(Performance)工具查看每个函数的执行性能并优化

    字体优化

    FOUT(Flash of Unstyled Text)等待一段时间,如果没加载完成,先显示默认。加载 后再进行切换。
    FOIT(F1ash of Invisib1e Text) 字体加载完毕后显示,加载超时降级系统字体(白 屏)

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <style>
    8. @font-face {
    9. font-family: 'hagan';
    10. src: url('./font.ttc');
    11. font-display: swap;
    12. /* b1ock 35 内不显示,如果没加载完毕用默认的 */
    13. /* swap 显示老字体 在替换*/
    14. /* fa11back 缩短不显示时间,如果没加载完毕用默认的,和b1ock类似*
    15. /* optional 替换可能用字体 可能不替换*/
    16. }
    17. article {
    18. font-family: hagan;
    19. }
    20. </style>
    21. <title>Document</title>
    22. </head>
    23. <body>
    24. <article>ABC abc</article>
    25. </body>
    26. </html>

    浏览器存储优化策略

    Cookie

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

  • 需要合理设置cookie有效期

  • 根据不同子域划分cookie来减少cookie传输
  • 静态资源域名和cookie域名采用不同域名,避免静态资源请求携带cookie。

    LocalStorage

    SessionStorage

    IndexDB

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

    其他优化策略
  • 关键资源个数越多,首次页面加载时间就会越长

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

    使用PWA提高用户体验

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

  • Web App Manifest 将网站添加到电脑桌面、手机桌面,类似Native的体验。

  • Service Worker 配合Cache API,能做到离线缓存各种内容。
  • Push API 配合 Notification API,能做到类似Native的消息推送与实时提醒。
  • App Shell 配合 App Skeleton,能做App壳与骨架屏