性能优化

测试性能工具 Audits

1. 页面加载时间

使用 PerformanceTiming 接口,它提供了在加载和使用当前页面期间发生的各种事件的性能计时信息。

可以通过 window.performance.timing 获取信息。

  • navigationStart 从同一个浏览器上下文的上一个文档卸载(unload)结束时的时间戳
  • domLoading 当前网页 DOM 结构开始解析时间戳
  • domContentLoadedEventEnd 所有需要立即执行的脚本已经被执行时的时间戳
  • domComplete 当前文档解析完成的时间戳

JS 代码

let startT = window.performance.timing.navigationStart;
let domStartT = window.performance.timing.domLoading;

window.onload = () => {
  let endT = window.performance.timing.domComplete;
  let loadTime = endT - startT;

  console.log("开始加载-->:", startT);
  console.log("解析Dom开始-->:", domStartT);
  console.log("文档解析完成-->:", endT);
  console.log(
    "%c开始加载至解析Dom开始的时间:" + (domStartT - startT),
    "color:green"
  );
  console.log("%c解析文档完成时间:" + (endT - domStartT), "color:purple");
  console.log("%c加载页面总时间:" + (endT - startT), "color:red");
  console.log("Page load time is : " + loadTime);
};

在谷歌浏览器配置代码块使用 Command 命令进行快捷操作 链接

2. 页面加载状态

  • loading / 正在加载
  • interactive / 可交互
    • 文档已被解析,"正在加载"状态结束,但是图像,样式表和框架之类的子资源仍在加载
  • complete / 完成
    • 文档和所有子资源已完成加载。表示 load 状态的事件即将被触发。
document.onreadystatechange = function () {
  console.log("readyState--->: ", document.readyState);
};

3. DNS 预解析

DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP

<link rel="dns-prefetch" href="//yuchengkai.cn" />

4. 预加载

预加载其实是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载 一定程度上降低首屏的加载时间,兼容性不好。

  • 基于 link 标签的 DNS-prefetch、subresource、preload、 prefetch、preconnect、prerender
  • 本地存储 localStorage
<link rel="preload" href="http://example.com" />

5. 懒加载

懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。

  • 对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。

  • 懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。

6. CDN

CDN 的概念

CDN(Content Delivery Network,内容分发网络)是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。

典型的 CDN 系统由下面三个部分组成:

  • 分发服务系统:最基本的工作单元就是 Cache 设备,cache(边缘 cache)负责直接响应最终用户的访问请求,把缓存在本地的内容快速地提供给用户。同时 cache 还负责与源站点进行内容同步,把更新的内容以及本地没有的内容从源站点获取并保存在本地。Cache 设备的数量、规模、总服务能力是衡量一个 CDN 系统服务能力的最基本的指标。
  • 负载均衡系统:主要功能是负责对所有发起服务请求的用户进行访问调度,确定提供给用户的最终实际访问地址。两级调度体系分为全局负载均衡(GSLB)和本地负载均衡(SLB)。全局负载均衡主要根据用户就近性原则,通过对每个服务节点进行“最优”判断,确定向用户提供服务的 cache 的物理位置。本地负载均衡主要负责节点内部的设备负载均衡
  • 运营管理系统:运营管理系统分为运营管理和网络管理子系统,负责处理业务层面的与外界系统交互所必须的收集、整理、交付工作,包含客户管理、产品管理、计费管理、统计分析等功能。

CDN 的作用

CDN 一般会用来托管 Web 资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用 CDN 来加速这些资源的访问。

  1. 在性能方面,引入 CDN 的作用在于:

    • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
    • 部分资源请求分配给了 CDN,减少了服务器的负载
  2. 在安全方面,CDN 有助于防御 DDoS、MITM 等网络攻击:

    • 针对 DDoS:通过监控分析异常流量,限制其请求频率
    • 针对 MITM:从源服务器到 CDN 节点到 ISP(Internet Service Provider),全链路 HTTPS 通信

除此之外,CDN 作为一种基础的云服务,同样具有资源托管、按需扩展(能够应对流量高峰)等方面的优势。

CDN 的原理

CDN 和 DNS 有着密不可分的联系,先来看一下 DNS 的解析域名过程,在浏览器输入 www.test.com 的解析过程如下:

  1. 检查浏览器缓存
  2. 检查操作系统缓存,常见的如 hosts 文件
  3. 检查路由器缓存
  4. 如果前几步都没没找到,会向 ISP(网络服务提供商)的 LDNS 服务器查询
  5. 如果 LDNS 服务器没找到,会向根域名服务器(Root Server)请求解析,分为以下几步:
    • 根服务器返回顶级域名(TLD)服务器如.com,.cn,.org 等的地址,该例子中会返回.com 的地址
    • 接着向顶级域名服务器发送请求,然后会返回次级域名(SLD)服务器的地址,本例子会返回.test 的地址
    • 接着向次级域名服务器发送请求,然后会返回通过域名查询到的目标 IP,本例子会返回www.test.com的地址
    • Local DNS Server 会缓存结果,并返回给用户,缓存在系统中

CDN 的工作原理:

  1. 用户未使用 CDN 缓存资源的过程:

    • a. 浏览器通过 DNS 对域名进行解析(就是上面的 DNS 解析过程),依次得到此域名对应的 IP 地址
    • b. 浏览器根据得到的 IP 地址,向域名的服务主机发送数据请求
    • c. 服务器向浏览器返回响应数据
  2. 用户使用 CDN 缓存资源的过程:

    • a. 对于点击的数据的 URL,经过本地 DNS 系统的解析,发现该 URL 对应的是一个 CDN 专用的 DNS 服务器,DNS 系统就会将域名解析权交给 CNAME 指向的 CDN 专用的 DNS 服务器。
    • b. CND 专用 DNS 服务器将 CND 的全局负载均衡设备 IP 地址返回给用户
    • c. 用户向 CDN 的全局负载均衡设备发起数据请求
    • d. CDN 的全局负载均衡设备根据用户的 IP 地址,以及用户请求的内容 URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求
    • e. 区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的 IP 地址返回给全局负载均衡设备
    • f. 全局负载均衡设备把服务器的 IP 地址返回给用户
    • g. 用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端。

如果缓存服务器没有用户想要的内容,那么缓存服务器就会向它的上一级缓存服务器请求内容,以此类推,直到获取到需要的资源。最后如果还是没有,就会回到自己的服务器去获取资源。

CNAME(意为:别名):在域名解析中,实际上解析出来的指定域名对应的 IP 地址,或者该域名的一个 CNAME,然后再根据这个 CNAME 来查找对应的 IP 地址。

CDN 的使用场景

  1. 使用第三方的 CDN 服务:如果想要开源一些项目,可以使用第三方的 CDN 服务
  2. 使用 CDN 进行静态资源的缓存:将自己网站的静态资源放在 CDN 上,比如 js、css、图片等。可以将整个项目放在 CDN 上,完成一键部署。
  3. 直播传送:直播本质上是使用流媒体进行传送,CDN 也是支持流媒体传送的,所以直播完全可以使用 CDN 来提高访问速度。CDN 在处理流媒体的时候与处理普通静态文件有所不同,普通文件如果在边缘节点没有找到的话,就会去上一层接着寻找,但是流媒体本身数据量就非常大,如果使用回源的方式,必然会带来性能问题,所以流媒体一般采用的都是主动推送的方式来进行。

CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。

因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。

7. js 防抖、节流

当我们在 scroll 事件中执行事件处理函数时,每次 scroll 事件触发都会执行事件处理函数,这样无限制的调用执行很多次重复的操作,会加重浏览器的负担。此时我们可以采用 debunce(防抖)和 throttle(节流)的方式来减少事件调用频率,同时也不影响实际效果。

  • 防抖函数

    当频繁触发事件时,一定时间内没有再次触发事件,此时才会执行事件处理函数。

    如果设定的时间内,又一次触发了事件,就清除之前的处理函数,重新延时。

    let timeoutFun = null;
    
    window.addEventListener("scroll", () => {
      if (timeoutFun !== null) {
        clearTimeout(timeoutFun);
      }
    
      timeoutFun = setTimeout(() => {
        console.log("执行事件处理");
      }, 1000);
    });
    
  • 函数节流

    使得一定时间内只出触发一次函数。

    原理:通过判断是否到达一定时间来触发函数。

    • 在节流函数内部使用开始时间 startTime 、当前时间 curTime 与 delay 来计算剩余时间 remaining。
    • 当 remaining <= 0 时表示该执行事件处理函数了(保证了第一次触发事件就能立即执行事件处理函数和每隔 delay 时间执行一次事件处理函数)。
    • 如果还没到时间的话就设定在 remaining 时间后再触发 (保证了最后一次触发事件后还能再执行一次事件处理函数)。
    • 当然在 remaining 这段时间中如果又一次触发事件,那么会取消当前的计时器,并重新计算一个 remaining 来判断当前状态。

      let timer = null;
      let startTime = Date.now();
      
      window.addEventListener("scroll", () => {
        let curTime = Date.now();
        let remaining = 1000 - (curTime - startTime);
        clearTimeout(timer);
      
        if (remaining <= 0) {
          console.log("第一触发事件 执行函数");
          startTime = Date.now();
        } else {
          timer = setTimeout(() => {
            console.log("settimeout 执行函数");
          }, remaining);
        }
      });
      
  • 区别

    • 函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在 delay 时间后触发函数,但是在 delay 时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

    • 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

8. react 性能优化

总结: 减少计算、渲染和请求

  • 减少不必要的渲染
    • shouldComponentUpdate
    • pureComponent
    • React.memo 函数组件使用
  • 数据缓存
    • useMemo 缓存参数
    • useCallback 缓存函数
    • 函数、对象尽量不要使用内联的方式
    • Router 中的内联函数渲染的时候使用 render 或者 children,不要使用 component
      • 当使用 component 的时候,Router 会用你指定的组件和 React.createElement 创建一个新的[react element]。这意味着当你提供一个内联函数的时候,每次 render 都会创建一个新的组件。这会导致不再更新已经现有的组件,而是直接卸载然后再去挂在一个新的组件。因此用到内联函数的内联渲染时,请使用 render 或者 children
    • 懒加载,长列表分页
    • 减少 http 请求
  • 数据结构
    • 合并逻辑相关的 state,为 reducer

9. SSR

渲染过程在服务器端完成,最终的渲染结果 HTML 页面通过 HTTP 协议发送给客户端。

服务端渲染(SSR)除了 SEO 还有很多时候用作首屏优化,加快首屏速度,提高用户体验。但是对服务器有要求,网络传输数据量大,占用部分服务器运算资源。

Vue 的 Nuxt.js 和 React 的 next.js 都是服务端渲染的方法。

10. gzip

服务端配置 gzip 压缩后可大大缩减资源大小。 Nginx 配置方式

http {
  gzip on;
  gzip_buffers 32 4K;
  gzip_comp_level 6;
  gzip_min_length 100;
  gzip_types application/javascript text/css text/xml;
  gzip_disable "MSIE [1-6]\.";
  gzip_vary on;
}

11. 打包优化

tree shaking

// 比如日常使用lodash的时候
import _ from "lodash";

// 如果如上引用lodash库,在构建包的时候是会把整个lodash包打入到我们的bundle包中的。
import _isEmpty from "lodash/isEmpty";

如果如上引用 lodash 库,在构建包的时候只会把 isEmpty 这个方法抽离出来再打入到我们的 bundle 包中。 这样的化就会大大减少我们包的 size。所以在日常引用第三方库的时候,需要注意导入的方式。 如何开启摇树

在 webpack4.x 中默认对 tree-shaking 进行了支持。

powered by Gitbook该文件修订时间: 2024-07-04 16:09:46

results matching ""

    No results matching ""