拉勾《前端性能优化方法与实战》笔记

undefined

Posted by NeptLiang on September 15, 2022

性能瓶颈点:从URL输入到页面加载过程分析

  1. DNS查询
  2. TCP三次握手和TLS协商,建立TCP连接
  3. 发起HTTP请求
  4. 服务端对请求进行响应
  5. 浏览器从响应结果中拿到数据,并进行解析和渲染

大致可以分为三个阶段:

  1. 客户端发起请求
  2. 服务端处理请求
  3. 客户端页面渲染

客户端请求阶段的瓶颈点

发起请求阶段是指:

  1. 本地缓存确认是否已经存在这个网站
  2. 如果没有,DNS查询,TCP三次握手和TLS协商,发起HTTP请求

在这个过程中,本地缓存DNS查询HTTP请求很容易成为影响前端性能的瓶颈点。

本地缓存

让静态资源加载更快

我们知道,本地缓存可以让静态资源加载更快,当客户端发起一个请求时,静态资源可以直接从客户端中获取,不需要再向服务器请求。而想要让本地缓存发挥这个作用,就需要先在服务器上进行配置。

  • 本地缓存一般包括

    • 强缓存
    • 协商缓存

    两种形式。

  • 强缓存是指浏览器在加载资源时

    1. 根据请求头的 expirescache-control 判断是否命中客户端缓存
    2. 如果命中,则直接从缓存读取资源,不会发请求到服务器,反之还需要走完整的资源请求流程
  • 协商缓存是指

    1. 浏览器会先发送一个请求到服务器
    2. 通过 last-modifiedetag 验证资源是否命中客户端缓存。
    3. 如果命中,服务器会将这个请求返回,但不会返回这个资源的数据,依然是从缓存中读取资源。 如果没有命中,无论是资源过期或者没有相关资源,都需要向服务器发起请求,等待服务器返回这个资源。

DNS查询

每进行一次 DNS 查询,都要经历从手机到移动信号塔,再到认证 DNS 服务器的过程。这中间需要很长的时间。

想要节省时间,一个办法就是让 DNS 查询走缓存。幸好浏览器提供了 DNS 预获取的接口,我们可以在打开浏览器或者 WebView 的同时就进行配置

HTTP 请求

在 HTTP 请求阶段,最大的瓶颈点来源于请求阻塞。

所谓请求阻塞,就是浏览器为保证访问速度,会默认对同一域下的资源保持一定的连接数,请求过多就会进行阻塞

那么,浏览器同域名的连接数限制是多少呢?一般是 6 个。如果当前请求数多于 6 个,只能 6 个并发,其余的得等最先的请求返回后,才能做下一次请求。

所以我们在项目之初,做一些域名规划很重要。我们可以先看看当前页面需要用到哪些域名,最关键的首屏中需要用到哪些域名,规划一下这些域名发送的顺序

除了域名规划,为了解决同域名下的阻塞问题,还可以做域名散列,通过不同的域名,增加请求并行连接数。常见做法是,将静态服务器地址 pic.google.com,做成支持 pic0-5 的 6 个域名,每次请求时随机选一个域名地址进行请求。因为有 6 个域名同时可用,最多可以并行 36 个连接。当然,这个域名个数不是越多越好,太分散的话,又会涉及多域名之间无法缓存静态资源的问题。

服务端数据处理阶段的瓶颈点

服务端数据处理阶段,是指 WebServer 接收到请求后,从数据存储层取到数据,再返回给前端的过程。

这个过程中的瓶颈点,就在于

  • 是否做了数据缓存处理
  • 是否做了 Gip 压缩
  • 是否有重定向

其中,Gzip 压缩是一种压缩技术,服务器端通过使用 Gzip 压缩传输到浏览器端的文本类资源(有别于图片等二进制资源)的大小可以变为原来的 1/3 左右

多数情况下,运维团队都会默认开启 Gzip 压缩

数据缓存

数据缓存分为两种:借助 Service Worker 的数据接口缓存、借助本地存储的接口缓存和CDN(Content Delivery Network,内容分发网络)

  • 其中 Service Worker 是浏览器的一个高级属性,本质上是一个请求代理层,它存在的目的就是拦截和处理网络数据请求。如果没有 Service Worker,请求每次直接落到 WebServer 上,需要走一次后端数据存取链路的流程,这会延长页面打开时间。

  • 借助本地存储的接口缓存是指,在一些对数据时效性要求不高的页面,第一次请求到数据后,程序将数据存储到本地存储(store 或者 localStorage、甚至客户端本身的存储)

  • 所谓 CDN,它的基本思路是,通过在网络各处放置节点服务器,构造一个智能虚拟网络,将用户的请求导向离用户最近的服务节点上

重定向

在服务端处理阶段,重定向分为三类:

  • 服务端返回的302重定向
  • META 标签实现的重定向
  • 前端 JavaScript 通过 window.location 实现的重定向。

它们都会引发新的 DNS 查询,导致新的 TCP 三次握手和 TLS 协商,以及产生新的 HTTP 请求

页面解析和渲染阶段的瓶颈点

在页面加载过程中,当前服务端对数据加工聚合处理后,客户端拿到数据,接下来就会进入解析和渲染阶段。

  • 所谓解析,就是 HTML 解析器把页面内容转换为 DOM 树和 CSSOM 树的过程。

    • DOM 树描述了标签之间的层次和结构。HTML 解析器通过词法分析获得开始和结束标签,生成相应的节点和创建节点之间的父子关系结构,直到完成 DOM 树的创建。

    • 而 CSSOM 树,即 CSS 对象模型。主要描述样式集的层次和结构,HTML 解析器遇到内联的 style 标签时,会触发 CSS 解析器对样式内容进行解析,与上面解析器解析 HTML 过程类似,CSS 解析器遍历其中每个规则,将 CSS 规则解析为浏览器可解析和处理的样式集合,最终结合浏览器里面的默认样式,汇总形成具有父子关系的 CSSOM 树。

  • 解析完后就是渲染。主线程会计算 DOM 节点的最终样式,生成布局树。布局树会记录参与页面布局的节点和样式。完成布局后,紧跟着就是绘制。绘制就是把各个节点绘制到屏幕上的过程,绘制结果以层的方式保存。当文档中各个部分以不同的层绘制时,相互重叠时,就必须进行合成,以确保他们可以以正确的顺序绘制和展示。

以上就是解析和渲染阶段,这个阶段流程环节多,逻辑复杂,瓶颈点也多,比如,DOM 树构建过程,CSSOM 树生成阶段,重排和重绘过程等

构建 DOM 树的瓶颈点

解析器构建 DOM 树的过程中,有三点会严重影响前端性能。

  • 一个是当 HTML 标签不满足 Web 语义化时,浏览器就需要更多时间去解析 DOM 标签的含义。特别是解析器对标签的容错,比如将 <br> 写成了 </br>,又或者表格嵌套不标准标签层次结构复杂等。遇到这些情况时,浏览器会进行语法纠错。这就会导致页面总的解析和渲染阶段需要更长的时间,严重影响页面展示性能。

  • 另一个是 DOM 节点的数量越多,构建 DOM 树的时间就会变长,进而延长解析时间,拖慢页面展示速度。

  • 最后一个是文档中包含 <SCRIPT> 标签时的情况。因为无论是 DOM 或者 CSSOM 都可以被 JavaScript 所访问并修改,所以一旦在页面解析时遇到 <SCRIPT> 标签,DOM 的构造过程就会暂停,等待服务器请求脚本。在脚本加载完成后,还要等取回所有的 CSS 及完成 CSSOM 之后才继续执行。

    这个过程中用户看到的是白色的屏幕。因此外部 <SCRIPT> 常被称为“解析”阶段的拦路虎。有时就因为解析过程中多了一个 <SCRIPT>,造成页面解析阶段从 200ms 变为 1s。

    所以,外部脚本的加载时机一定要确定好,能够延迟加载就选用延迟加载。另外,我们可以通过使用 defer 和 async,告诉浏览器在等待脚本下载期间不阻止解析过程,这样做可以明显提升性能。

布局中的瓶颈点

在布局阶段,浏览器会根据样式解析器给出的样式规则,来计算某个元素需要占据的空间大小和屏幕中的位置(比如电商详情页一张 banner图片的高度、宽度和位置),借助结算结果,来进行布局。而主线程布局时,使用的是流模型的布局方式。所谓流模型,就是像水流一样,需要从左到右,从上到下遍历一遍所有元素。

假设我们在页面渲染过程运行时修改了一个元素的属性,比如在电商的商品详情页加入一条广告数据。这时布局阶段受到了影响。浏览器必须检查所有其他区域的元素,然后自动重排页面,受到影响的元素需要重新绘制,最后还得合成,相当于整个渲染流程再来了一遍。

除此之外,因为浏览器每次布局计算都要作用于整个 DOM,如果元素量大,计算出所有元素的位置和尺寸会花很长的时间。所以布局阶段很容易成为性能瓶颈点。

还有其他方面,比如,偏硬件领域,像 GPU 绘图、操作系统 GUI 和 LCD 显示等瓶颈点;网络层和服务层,如拥塞预防、负载均衡和慢启动;还有一些页面解析和渲染的算法,如解析算法、标记化算法和树构建算法,等等

案例分析:移动端 M 站性能优化落地注意事项

  • 通过本地缓存方案来减少客户端请求数,同时让运维同事对静态资源配置了合理的过期时间,实现静态资源的强缓存方案

  • 对页面做了 DNS 预解析处理

  • 对一些广告和帮帮页(WebIM 应用,用于用户的咨询使用)做了懒加载

  • 为了确保页面解析不会中断,对列表页的加载资源做了处理,也制定了 CSS 相关的规范,确保嵌套层次更少,让规则解析更简单。

  • 原先列表页的筛选功能使用的是同步技术,每次完成筛选,整个页面都需要重新加载一次,这一次把它修改成了 SPA 单页面应用,每次筛选后,只请求数据接口即可,不用整个页面都请求。

列表页改单页面应用

这些页面都在搜索引擎和其他 App 做了广告投放,需要考虑 SEO, 也就是说,URL 要和原来的页面保持一致的同时,支持爬虫够抓取和解析。

调研过程中发现,与直接切换 VUE/React 技术栈相比,现有的列表页模板修改成单页面应用,风险上要小很多,所以最终采用的是列表页模板的单页面实现。

浏览器的 History API 方案刚好能够支持单页面应用和原页面URL保持一致。具体来说,通过其中的 pushState 修改 URL 和历史记录,能确保筛选后的 URL 和原来保持一致,通过它的回退等功能操作,也可以回到上一个页面。

对于SEO这块儿,为了确保爬虫能够抓取解析这个页面,额外提供了一套服务端模板,如果页面访问头是爬虫的话,就走服务端模板,反之如果是个人用户的话,就走单页面应用

弱网下性能优化

  • 因为弱网下一个请求都需要 2s,而原来的列表页数据接口请求大约26个请求,于是就尽可能合并请求,最后合并到了11个。

  • 小图标采用 Base64 Encoding 的方式,内嵌于页面中,不用额外发网络请求来获取。

  • 针对弱网(特别是移动网络),不自动加载图片,只显示占位图。

如何实现监控预警并进行问题诊断

问题诊断

诊断清单:

  • 全量 vs 增量

    以京东 App 列表页为例,首屏一般展示 4 条左右的商品数据,PC 页首屏展示 50 条商品数据,后端数据接口一般是同一套(无论是针对移动端还是 PC 端),这时 App 列表页请求后端接口,后端一次返回 50 条数据就不合适了。更好的做法是,分接口先拉取首屏所需的 4 条数据,然后在页面滚动或者下拉操作加载后续数据即可。

    值得注意的是,为了保持体验流畅,有时候我们会多加载几条,比如开始加载 6 条,刚开始滚动到首屏结束时,第 5 条数据已经有了(此时请求第 7 条数据),不需要等后端服务器返回就可以展示。

  • 同步 vs 异步

    比如电商 App 列表页,一般需要先去拉取导航位置图片和链接信息,然后去拉取商品列表信息,这会延长加载时间。此时我们可以考虑同时拉取两个列表,中间有依赖关系的地方,集中到第一个接口中去获取,然后就可以并行去请求两个接口了。

  • 实时 vs 缓存

    如果是能够缓存的数据(如双 11 的榜单、页面中的 JS、CSS 资源),建议优先检查一下是否做了缓存处理

    有关静态资源(如 JS、CSS文件)缓存处理,比较好的方法是,将每次渲染后的页面做缓存,页面打开时就展示,然后局部细微做更新

  • 原片 vs 压缩

    做一些无损或者有损压缩处理,以此提高加载速度,或者尽量不使用原始图片(如 png-24),优先使用 webp 等图片格式

优化手段:首屏秒开的 4 重保障

4 个方法——

  • 懒加载
  • 缓存
  • 离线化
  • 并行化

懒加载

懒加载是性能优化的前头兵。比如当我们打开一个页面,它的内容超过了浏览器的可视窗口大小,我们可以先加载前端的可视区域内容,剩下的内容等它进入可视区域后再按需加载。

如果首屏只需要几条数据,后端接口一次可以吐出50条数据,这会导致请求时间过长,首屏特别慢。这种情况就非常适合用懒加载方案去解决。

具体怎么做呢?我们可以先根据手机的可视窗口,估算需要多少条数据,比如京东 App 列表页是 4 条数据,这时候,先从后端拉取 4 条数据进行展现,然后超出首屏的内容,可以在页面下拉或者滚动时再发起加载。

那么如果首页当中图片比较多,比如搜索引擎产品的首页,如何保证首屏秒开呢?同样也可以采用懒加载。以百度图片列表页为例,可视区域范围内的图片先请求加载,一般会根据不同手机机型估算一个最大数据,比如 ihone12 Pro 屏幕比较大, 4 行 8 条数据,我们就先请求 8 条数据,用来在可视区域展示,其他位置采用占位符填充,在滑动到目标区域位置后,才使用真实的图片填充。

缓存

如果说懒加载本质是提供首屏后请求非关键内容的能力,那么缓存则是赋予二次访问不需要重复请求的能力。在首屏优化方案中,接口缓存和静态资源缓存起到中流砥柱的作用。

接口缓存

接口缓存的实现,如果是端内的话,所有请求都走 Native 请求,以此来实现接口缓存。为什么要这么做呢?

App 中的页面展现有两种形式,使用 Native 开发的页面展现和使用 H5 开发的页面展现。如果统一使用 Native 做请求的话,已经请求过的数据接口,就不用请求了。而如果使用 H5 请求数据,必须等 WebView 初始化之后才能请求(也就是串行请求),而 Native 请求时,可以在 WebView 初始化之前就开始请求数据(也就是并行请求),这样能有效节省时间。

静态资源缓存

数据接口的请求一般来说较少,只有几个,而静态资源(如 JS、CSS、图片和字体等)的请求就太多了。以京东首页为例,177 个请求中除了 1 个文档和 1 个数据接口外,其余都是静态资源请求。

那么,如何做静态缓存方案呢?这里有两种情况,一种是静态资源长期不需要修改,还有一种是静态资源修改频繁的

  • 资源长期不变的话,比如 1 年都不怎么变化,我们可以使用强缓存,如 Cache-Control 来实现。具体来说可以通过设置 Cache-Control:max-age=31536000,来让浏览器在一年内直接使用本地缓存文件,而不是向服务端发出请求。

  • 资源本身随时会发生改动的,可以通过设置 Etag 实现协商缓存。具体来说,在初次请求资源时,设置 Etag(比如使用资源的 md5 作为 Etag),并且返回 200 的状态码,之后请求时带上 If-none-match 字段,来询问服务器当前版本是否可用。如果服务端数据没有变化,会返回一个 304 的状态码给客户端

离线化

离线化是指线上实时变动的资源数据静态化到本地,访问时走的是本地文件的方案。说到这里,你是不是想到了离线包?离线包是离线化的一种方案,是将静态资源存储到 App 本地的方案,不过,在这里,重点讲的是离线化的另一个方案——把页面内容静态化到本地。

离线化一般适合首页或者列表页等不需要登录页面的场景,同时能够支持 SEO 功能。那么,如何实现离线化呢?其实,打包构建时预渲染页面,前端请求落到 index.html 上时,已经是渲染过的内容。此时,可以通过 Webpack 的 prerender-spa-plugin 来实现预渲染,进而实现离线化

并行化

懒加载、缓存和离线化都是在请求本身上下功夫,想尽办法减少请求或者推迟请求,并行化则是在请求通道上功夫,解决请求阻塞问题,进而减少首屏时间。 我们在处理请求阻塞时,也可以加大请求通道数量——借助于HTTP 2.0 的多路复用方案来解决。

HTTP 1.1 时代,有两个性能瓶颈点,串行的文件传输和同域名的连接数限制(6个),到了HTTP 2.0 时代,因为提供了多路复用的功能,传输数据不再使用文本传输(文本传输必须按顺序传输,否则接收端不知道字符的顺序),而是采用二进制数据帧和流的方式进行传输。

其中,帧是数据接收的最小单位,流是连接中的一个虚拟通道,它可以承载双向信息。每个流都会有一个唯一的整数 ID 对数据顺序进行标识,这样接收端收到数据后,可以按照顺序对数据进行合并,不会出现顺序出错的情况。所以,在使用流的情况下,不论多少个资源请求,只要建立一个连接即可。

文件传输环节问题解决后,同域名连接数限制问题怎么解决呢?以 Nginx 服务器为例,原先因为每个域名有 6 个连接数限制,最大并发就是 100 个请求,采用 HTTP 2.0 之后,现在则可以做到 600,提升了 6倍。

你一定会问,这不是运维侧要做的事情吗,我们前端开发需要做什么?我们要改变静态文件合并(JS、CSS、图片文件)和静态资源服务器做域名散列这两种开发方式

具体来说,使用 HTTP 2.0 多路复用之后,单个文件可以单独上线,不需要再做 JS 文件合并了。因为原先遇到由 A 和 B 组成的 C 文件,其中 A 文件稍微有点修改,整个C 文件就需要重新加载的情况,如今由于没有同域名连接数限制了,也就不需要了。

此外,为了解决静态域名阻塞,提升请求并行能力,需要将静态域名分为 pic0-pic5。虽然通过静态资源域名散列的办法解决了问题,但这样做的话,DNS 解析时间会变长很多,同时还需要额外的服务器来满足,如今,采用 HTTP 2.0 多路复用之后,也不需要这样做了

在实际工作当中,前端工程师还会用到离线包和 SSR。但 SSR 的实现比较重,要做的改造比较多,要求开发者对 node 生态有很好理解和把握,而离线包依赖于 App 端内的环境,对于端外和 PC 站不具有普适性

优化手段:白屏 300ms 和界面流畅优化技巧

所谓白屏时间,一般是当用户打开一个页面,从开始等待到页面第一个字符出现的时间,白屏时间越短,给人感觉 App 速度快,体验好,能有效降低跳出率

白屏优化

基于影响白屏时间长短的两个主要因素来解决——

  • DNS 查询
  • 首字符展示

DNS 查询优化

  • 前端侧,可以通过在页面中加入 dns-prefetch,在静态资源请求之前对域名进行解析,从而减少用户进入页面的等待时间。如下所示:

      <meta http-equiv="x-dns-prefetch-control" content="on" />
      <link rel="dns-prefetch" href="https://s.google.com/" >
    

    其中第一行中的 x-dns-prefetch-control 表示开启 DNS 预解析功能,第二行 dns-prefetch 表示强制对 s.google.com 的域名做预解析。这样在 s.google.com 的资源请求开始前,DNS 解析完成,后续请求就不需要重复做解析了。它可以为你减少 150ms 左右的 DNS 解析时间。

  • 以上是一个轻量级的方案,通过它可以将 DNS 解析时间控制在 400ms以内(这个算是比较快的)。如果你想要将耗时进一步压缩,比如控制在 200ms,此时就需要一个重量级的方案了。具体来说,可以采用 IP 直连方式,原来是请求www.google.com,现在我们通过调用 SDK 进行域名解析,拿到对应的 IP(如 6.6.6.6),然后直接请求这个 IP 地址拿到数据。

    当然,这个实现起来需要避过许多坑,比如,HTTPS 证书和配置文件。

    • Https 证书是指当客户端使用 IP 直连时,请求 URL 中的 host 会被替换成对应的 IP,所以在证书验证时,会出现 domain 不匹配的情况,导致 SSL/TLS 握手不成功。

      怎么解决呢?在非 SNI(Server Name Indication,表示单 IP多域名)的场景下,可以把证书验证环节独立出来 (如 Hook证书校验环节),然后将 IP 替换为原来的域名。在 SNI 场景下,可以定制 SSLSocketFactory,在 createSocket 时替换为 IP,并进行 SNI/HostNameVerify 配置。

    • 而配置文件方面,一般在域名只有两三个的情况时,我们可以用到它来做 IP 和域名的映射。但随着机房的扩大,每次扩机器都要升级配置文件,后续会非常麻烦。

      对此我们可以采用 httpDNS 来解决。这是因为 httpDNS 可以准确调度到对应区域的服务器 IP 地址给用户,同时还可以避免运行商 DNS 劫持。具体来说,SDK 会通过发报文(类似系统向 DNS 运营商发的报文)向 httpDNS 做一个 HTTP 请求(也是通过 IP 直接请求),请求通过后拿到对应域名,然后进行 IP 直连,完成资源或者数据接口请求。

首字符展示优化

所谓首字符展示,通常我们会在页面加载过程中出现一个 loading 图,用来告诉用户页面内容需要加载,请耐心等待。但这样一个 loading 图既无法让用户感受到页面加载到什么程度,也无法给用户视觉上一个焦点,让人们的注意力集中在上面。

如何解决这个问题呢?我们可以使用骨架屏。骨架屏并没有真正减少白屏时间,但是给了用户一个心理预期,让他可以感受到页面上大致有什么内容。

那么,如何构建骨架屏呢?因为考虑到每次视觉修改或者功能迭代,骨架屏都要配合修改,建议采用自动化方案,而不是手动骨架屏方案(也就是自己编写骨架屏代码)。骨架屏的实现方法有以下三个步骤。

  1. 步骤一,确定生成规则,遍历所有的 DOM 元素。针对特定区块(如视频、音频)生成相应的代码块,获取原始页面中 DOM 节点的宽度、高度和距离视窗的位置,计算出当前设备快高对应的大小,转换成相应的百分比,然后来适配不同的设备。

  2. 步骤二,基于上述规则结合 CLI 工具可以通过脚手架自动生成骨架屏

  3. 步骤三,将骨架屏自动化注入页面,再利用 Puppeteer 把骨架屏代码注入页面中自动运行。整个过程比较复杂,且有不少坑。

卡顿治理

首先也还是问题的定位,先通过 charles 等工具抓包看一下数据接口,

  • 如果是和数据相关的问题,找后端同事,或者用数据缓存的方式解决。
  • 如果问题出在前端,一般和以下两种情形有关:

    • 浏览器的主线程与合成线程调度不合理
    • 以及计算耗时操作

浏览器的主线程与合成线程调度不合理

比如,在某电商 App 页面点击抽奖活动时,遇到一个红包移动的效果,在红包位置变化时,页面展现时特别卡,这就是主线程和合成线程调度的问题。怎么解决呢?

一般来说,

  • 主线程主要负责运行 JavaScript,计算 CSS 样式,元素布局,然后交给合成线程,
  • 合成线程主要负责绘制

使用 heightwidthmarginpadding 等作为 transition 值时,会让主线程压力很大。此时我们可以使用 transform 来代替直接设置 margin 等操作

比如红包元素从 margin-left:-10px 渲染到 margin-left:0,主线程需要计算样式 margin-left:-9pxmargin-left:-8px,一直到 margin-left:0,每一次主线程计算样式后,合成线程都需要绘制到 GPU 再渲染到屏幕上,来来回回需要进行 10 次主线程渲染,10 次合成线程渲染,这给浏览器造成很大压力,从而出现卡顿。

如何解决呢?我们可以利用 transform 来做,比如 tranform:translate(-10px,0)transform:translate(0,0),主线程只需要进行一次tranform:translate(-10px,0)transform:translate(0,0),然后合成线程去一次将 -10px 转换到 0px。这样的话,总计 11 次计算,可以减少 9 步操作,假设一次 10ms,将减少 90ms。

计算耗时操作

遇到这类问题,一般有两种解法:空间换时间和时间换空间

  • 空间换时间方面,比如你需要频繁增加删除很多 DOM 元素,这时候一定会很卡,在对 DOM 元素增删的过程中最好先在 DocumentFragment(DOM文档碎片)上操作,而不是直接在DOM上操作。只在最后一步操作完成后,将所有 DocumentFragment 的变动更新到DOM上,从而解决频繁更新 DOM 带来的卡顿问题。

  • 至于时间换空间,一般是通过将一个复杂的操作细分成一个队列,然后通过多次操作解决复杂操作的问题。

    比如 2010 年时,一个 WebIM 离线消息的项目,其中一个功能是将批量消息下载到本地,点击下载后,由于文件过大,浏览器出现卡顿甚至卡死的情况。怎么办?最后通过实现一个 Chunk 方法,创建了一个队列,定时取出队列的任务,然后在本地服务器上将内容再次合并的方式来解决。代码示例如下:

      function chunk(array, process, context) {
          setTimeout(function () {
              //shift方法可以获取队列中下一个要处理的项目
              var item = array.shift();
              //通过call调用的process函数,这样可以设置一个合适的执行环境(如果必须)
              process.call(context, item);
              if (array.length > 0) {
                  setTimeout(arguments.callee, 100);
              }
          }, 100);
      }
    

    具体来说,是通过 array.shift 将数组切分为不同的队列任务,调用 setTimeout 方法设置 100ms 的延时,最后将调用 process.call 方法去执行,从而解决了复杂任务带来的卡顿问题。

    在实际使用时,尤其是做动画时候要注意,Settimeout 设置定时时间不准(比如设置 0ms 延迟,实际中会延迟 4ms 左右),如果时间间隔过小(如小于 16ms),还会出现因为掉帧导致的卡顿,所幸新版本浏览器(IE10+)提供了requestAnimationFrame方法,我们可以封装一个兼容性方法较好解决它。

优化方案的选择

很多同学会因为缺乏对节奏的把握,影响优化结果,比如明明时间很紧,要快点拿到结果,你却选择了一个长期的方案,导致迟迟看不到结果,项目被 cancel 掉。

一般来说,业务报出性能问题时,比如手机首页访问慢的问题,需要一个短平快的解决方案(比如一周内需要完成上线),快速上线。

因为时间的原因,这个性能优化方案过程中肯定是要有取舍的,比如在最终效果方面,性能指标定到首屏平均 1s 就可以了,更多是短期方案,也就是前端工程师可以独立完成的优化手段,如懒加载、离线化、异步化、骨架屏和缓存等手段


//End of Article


公众号二维码