深度剖析前端性能优化CRP

1,068 阅读7分钟

深度剖析前端性能优化CRP

CRP(Critical [ˈkrɪtɪkl] Rendering [ˈrendərɪŋ] Path,关键渲染路径)

一次完整页面请求所发生的事情

  1. URL解析

    • 地址解析和编码
    • HSTS (HTTP严格传输安全协议)
    • 缓存检查
    • 。。。

    解析步骤及分类:

    1. 浏览器首先会看是否有缓存,若没有缓存,则浏览器会直接向服务器发送请求,然后服务器返回请求结果以及缓存标识,存入缓存,然后页面加载成功
    2. 如果有缓存,则看缓存是否过期(Expires 和 Cache-Control),若缓存没过期,则直接读取缓存,然后返回缓存,最后页面加载成功
    3. 如果有缓存,但是缓存过期了,则需要携带标识(if-Modified-Since和if-None-Match),向服务器发送请求,此时服务器会查看资源是否有更新,(1)若没有更新,则返回304 状态码,继续使用缓存->读取缓存->返回缓存->页面加载成功;(2)若资源已经更新,则重新返回资源和缓存标识,返回状态码为200,并存入缓存中,最后页面加载成功
  2. DNS解析

  1. TCP 三次握手

  1. 发送HTTP请求,服务器处理请求,返回响应结果
  2. TCP 四次挥手

  1. 浏览器渲染
  2. 代码运行中

关键流程一:浏览器渲染流程

1. 构建DOM树、CSSOM树、渲染树、

  • DOM树
    • 转换
    • 令牌
    • 词法分析
    • DOM构建

CSSOM 树

Render-Tree 渲染树

  • 总结步骤:
    • 处理 HTML 标记,构建 DOM 树
    • 处理 CSS 标记,构建 CSSOM 树
    • 将 DOM 树和 CSSOM 树融合成渲染树
    • 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流 => 布局(Layout)或 重排(reflow)
    • 根据渲染树以及回流得到的几何信息,得到节点的绝对像素 => 绘制(painting)或栅格化(rasterizing)

  • 优化方案:
    • 标签语义化和避免深层次嵌套
    • CSS选择器渲染是从右到左
    • 尽早尽快地把CSS下载到客户端(充分利用HTTP多请求并发机制)
      • style
      • link
      • @import
      • 放到顶部
    • 避免阻塞的JS加载
      • 放到底部
    • 减少DOM的回流和重绘

浏览器渲染原理

1. DOM的重绘和回流 Repaint & Reflow
  • 重绘:元素样式的改变(但宽高、大小、位置等不变)

    • 如:outline, visibility, color, background-color等
  • 回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染

    • 如添加或删除可见的DOM元素;元素的位置发生变化;元素的尺寸发生变化;内容发生变化(比如文本变化或者图片被另一个不同尺寸的图片所替代);页面一开始渲染的时候(这个无法避免);因为回流事根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流。。。 注意:回流一定会触发重绘,而重绘不一定会回流
2. 前端性能优化之:避免DOM的回流
  • 放弃传统操作DOM的时代,基于vue/react开始数据影响视图模式

    • mwm / mvc / virtual dom / dom diff ...
  • 分离读写操作(现代的浏览器都有渲染队列的机制)

    • offsetTop, offsetLeft, offsetWidth, offsetHeight, clientTop, clientLeft, clientWidth, clientHeight, scrollTop, scrollLeft, scrollWidth, scrollHeight, getComputedStyle, currentStyle... 会刷新渲染队列
  • 样式集中改变

    • div.style.cssText = 'width:20px;height:20px;'
    • div.className = 'box';
前端性能优化之:避免DOM的回流
  • 缓存布局信息

    • div.style.left= div.offsetLeft + 1 + 'px'; div.style.top = div.offsetTop + 1 + 'px'; => 改写为: var curLeft = div.offsetLeft; var curTop = div.offsetTop; div.style.left = curLeft + 1 + 'px'; div.style.top = curTop + 1 + 'px';
  • 元素批量修改

    • 文档碎片:createDocumentFragment
    • 模版字符串拼接
  • 动画效果应用到position 属性为 absolute 或 fixed 的元素上(脱离文档流)

  • CSS3 硬件加速(GPU加速)

    • 比起考虑如何减少回流重绘,我们更期望的事,根本不要回流重绘;transform, opacity, filters... 这些属性会触发硬件加速,不会引发回流和重绘...
    • 可能会引发的坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等
  • 牺牲平滑度换取速度

    • 每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去事跳动的,因为浏览器正在与更新回流做斗争。每次移动3像素可能看起来平滑度低了,但它不会导致CPU 在较慢的机器中抖动
  • 避免 table 布局和使用 css 的 javascript 表达式

关键流程二:网络交互层面上的优化

1. DNS方面的优化

每一次DNS解析时间预计在20~120毫秒

  • 减少DNS请求次数
  • DNS预获取(DNS Prefetch)
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//static.360buyimg.com"/>
<link rel="dns-prefetch" href="//misc.360buyimg.com"/>
<link rel="dns-prefetch" href="//img10.360buyimg.com"/>
<link rel="dns-prefetch" href="//d.3.cn"/>
<link rel="dns-prefetch" href="//d.jd.com"/>
2. 减少HTTP请求次数和请求资源大小
  • 资源合并压缩
  • 字体图标
  • Base64
  • GZIP(一般的文件能压缩60%多)
  • 图片懒加载
  • 数据延迟分批加载
  • CDN资源
3. 应用缓存

缓存位置

  • Service Worker:浏览器独立线程进行缓存
  • Memory Cache : 内存缓存
  • Disk Cache:硬盘缓存
  • Push Cache:推送缓存(HTTP/2中的)

打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配,如有则使用,如没有则发送网络请求。 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话),其次才是 disk cache。 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。

强缓存 Expires / Cache-Control

浏览器对于强缓存的处理:根据第一次请求资源时返回的响应头来确定的

  • Expires:缓存过期时间,用来指定资源到期的时间(HTTP/1)

  • Cache-Control:cache-control: max-age=2592000第一次拿到资源后的2592000秒内(30天),再次发送请求,读取缓存中的信息(HTTP/1.1)

两者同时存在的话,Cache-Control优先级高于Expires

协商缓存 Last-Modified / ETag

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程

  • 协商缓存生效,返回304和Not Modified

  • 协商缓存失效,返回200和请求结果

  1. Last-Modified和If-Modified-Since 第一次访问资源,服务器返回资源的同时,响应头中设置 Last-Modified(服务器上的最后修改时间),浏览器接收后,缓存文件和响应头; 下一次请求这个资源,浏览器检测到有 Last-Modified,于是添加If-Modified-Since请求头,值就是Last-Modified中的值; 服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200; 但是Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源;

  2. ETag和If-None-Match Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成;下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到请求头If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

数据缓存:LocalStorage本地存储