Skip to content
本页目录

从问题出发

在讨论性能优化前先来补充一点前置知识,了解一下如何评估性能?

最简单的你可以使用google推出的插件lighthouse,当然除了lighthouse之外,Web接口专门提供了相应的API供开发者使用,比如Performance API,我们来看看其中的PerformanceNavigationTiming中定义的所有时间戳属性:时间戳属性

有了前置知识,关于前端性能优化,我想从一个老生常谈的问题切入,什么问题呢?

从输入URL到看见页面发生了什么?

比如,当我们输入在浏览器地址栏输入https://muri.life/(或者直接点击此链接时)并按下Enter键,到页面显示出内容会依次经过以下过程:

  • 检查有无重定向
    性能优化
    • 尽量的避免重定向。
  • 查找缓存

    按照浏览器缓存 => 系统缓存 => 路由器缓存 => 运营商DNS缓存 => 根域名服务器 => 顶级域名服务器 => 主域名服务器的顺序逐步读取缓存。

    性能优化
    • DNS缓存优化。

      为什么不是先存放在浏览器缓存中,而是缓存到系统缓存中?

      • 浏览器缓存是浏览器自动设置的,有效时间一般为1分钟;
      • 系统缓存是开发者设置的,有效时间一般为TTL时长(默认为1小时)。
  • DNS解析

    若查找缓存失败,则开始解析DNS,获得对应域名的IP地址。

    性能优化
    • DNS预解析:提前解析之后可能会用到的域名,将解析结果缓存到系统缓存中,缩短DNS解析时间,进而提高网站的访问速度。

      如何配置DNS预解析?

      • 通过修改link标签rel属性<link rel="dns-prefetch" href="https://muri.life/">
      • 指定HTTP标头Link: <https://muri.life/>; rel=dns-prefetch
  • 发起TCP连接

    经过三次握手建立TCP连接。

    性能优化
    • 多花钱。
  • 发送HTTP请求

    请求报文请求行请求报头请求正文组成。

    性能优化
    • 使用CDN加速;
    • 减少资源大小:
      • 代码压缩;
      • 图片压缩;
      • 组件按需加载。
    • 减少HTTP请求数:
      • HTTP强缓存;
      • 本地存储,像localStorage
      • 合并请求,像雪碧图
  • 服务器处理请求并返回HTTP报文

    HTTP报文状态码响应报头响应报文组成。

    性能优化
    • Redis缓存;
    • 使用GzipBrotli压缩资源。
  • 浏览器解析渲染页面
    • 解析HTML生成DOM树,解析CSS生成CSSOM树

      虽然加载CSS不会阻塞DOM树解析,但是会阻塞DOM渲染和后面JavaScript语句的执行。

      性能优化
      • 合并多个CSS文件,或者直接将首屏关键CSS写成内联样式(内联样式不能被缓存)
      • 异步加载CSS
      JavaScript
      <link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
      // 或者
      <link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
      • @import会影响浏览器的并行下载,不要使用@import
    • 合并DOM树CSSOM树生成Render树

      由于Render树依赖CSSOM树,必须等待到CSSOM树构建完成,即CSS资源加载完成(或者CSS资源加载失败)后,才能开始渲染,这也是为什么加载CSS会阻塞DOM渲染
      以下两种情况JavaScript的执行会阻塞DOMCSSOM的构建:

      • JavaScript文件被放置在head标签内部(暂停HTML解析,先执行JavaScript);
      • JavaScript代码修改了DOM结构(回流和重绘)。
      性能优化
      • 使用defer或async
      JavaScript
      <script src='xxx' async></script>
      // 异步加载脚本,在文档渲染完毕后,DOMContentLoaded事件调用前按顺序执行
      <script src='xxx' defer></script>
      // 异步加载脚本,在允许的情况下,谁先加载完谁先执行
    • 根据Render树渲染并绘制页面。

      这里涉及了回流和重绘

      性能优化
      • 尽量避免回流和重绘。
  • 连接结束

    经过四次挥手断开连接。