当前位置:首页 > 前端 > 正文内容

前端性能优化的知识(上)

放牧的风1个月前 (06-15)前端129

前言

引言

反复看下以下三个问题。

  • 有木有不同的人问过你:什么是前端性能优化?

  • 有木有不同的面试官问过你:你为前端性能优化做过什么?

  • 有木有哪一次,你问过自己:别人问我前端性能优化到底应该如何答复?

  • 你有木有一套自己的关于性能优化的答案,能让技术大牛和你一起探讨,也能让小白点头称是。

假如你有,那你的答案在哪。

我们先来探讨一件事情,一个前端项目如何从构思到落地

例如整个天猫首页。答案有很多种,你看看有木有你想的那种。

  • create-react-app 初始化一个项目吧。 首页写好一些组件,例如豆腐块、Header、Bar、长滚动ScrollView。然后项目编译、打包上线。

  • webpack手搭一个项目。

    • 路由按需加载弄上,可无痕浏览的带loadmore的高性能ScrollList

    • 因为是首页,明显会牵扯到首屏幕加载的问题。那骨架屏给安排上吧。

    • 再搞一套webpack的最佳实践,目的是能最终得到体积尽量小的打包文件。

    • SSR来一套吧。因为首页DOM结构太复杂了,如果走Virtual DOM那一套,等到GPU渲染UI就太慢了。

以上,有命中你的某个点吗? 不管有木有道理,这是你想到的点吗?是不是总感觉好像少了点什么?

本篇文章不细讲其它内容。 但到这了就得提一嘴,留个印象。

  • 系统层设计

    • 一个新系统留20%来满足当前已有业务。 剩下80% 用来系统演进。可以思考下,天猫首页一直变,却依然能保证性能达标。

  • 业务层设计

    • 已有业务是否与其它业务产生了耦合,是否存在前置业务,如果有那前置业务的权限又在哪里。已有业务是否能根据系统演进程度不断兼容新业务?

  • 应用层设计

    • 例如微前端,例如组件库,例如npm install

    • webpack优化

    • 骨架屏

    • 路有动态按需加载

  • 代码层设计

    • 写好高性能React 代码?如何利用好Vue3 时间切片?

    • 不说了。太初级的浪费慢慢积累就好。

所以,你应该发现了“总感觉好像少了点什么” 是少在哪里了。对,前端性能优化不仅仅是应用层面、代码层的优化,更重要的是系统层、业务层的优化。

看了以上的心理预设,那接下来就进入正式进入主题吧。

性能优化流程

尝试着走完下面这个流程:

  • 性能指标设定(FPS、页面秒开率、报错率、请求数等)

    • 如何让老板了解你的优化方案?假如你的老板不懂技术。

    • 告诉老板,页面白屏时间减少了0.4s。

    • 告诉老板,弱网情况下首页秒开。

    • 告诉老板,以前http请求量大导致服务器压力太大了,现在每个页面最多只有不到5个请求是向服务器要资源的。

    • 告诉老板……

  • 性能标准确定

    • 确认要哪些指标

  • 收益评估

    • 面向老板满意编程。 /捂脸.png (手动狗头)

  • 诊断清单

    • 清单上会告诉你各项指标的数据。

  • 优化手段

    • WebView 性能优化

    • 并行初始化

    • 资源预加载

    • 数据接口请求优化

    • 前端架构性能调优

    • 长列表性能优化

    • 打包优化

    • 组件骨架屏

    • 图片骨架屏

    • 懒加载

    • 缓存

    • 离线化

    • 并行化

    • 保证首次加载为秒开的离线包设计

    • App 启动阶段的优化方案

    • 页面白屏阶段的优化方案

    • 首屏渲染阶段的优化方案

    • Hybrid APP 性能优化

    • 首评秒开的X种方法?

    • 骨架屏

    • NSR

    • SSR

    • webView层及代码架构层面优化

  • 性能立项

    • 确定了就去搞起来吧。

  • 性能实践

    • 做好准备,尽情的在各种恶劣环境下把页面快速的折腾出来吧!

小结

现在,你对前端性能优化有了一个完整的认知了吗?很多时候谈论到性能优化首先需要谈到如何对性能进行“确诊”。虽然大部分情况,你不会被人问到是如何对性能进行监控的。(说话声音越来越小。。。)

接下来就细谈优化手段


首屏秒开的多种优化手段

1. 懒加载

最常见的优化手段之一。

懒加载是指在长页面加载过程时,先加载关键内容,延迟加载非关键内容。比如当我们打开一个页面,它的内容超过了浏览器的可视区口大小,我们可以先加载前端的可视区域内容,剩下的内容等它进入可视区域后再按需加载。

举个栗子。天猫首页精选。上图。

bbc3c4895c16493a8e3f65b6bd9dc0fa_tplv-k3u1fbpfcp-watermark.image

正好是天猫618活动。这个IOS版的天猫首页精选。如果你经常访问天猫首页精选,你会发现它已经几乎做到了无痕浏览。懒加载在这就被运用的很好,当然,这里不仅仅是做了懒加载才达到这样的效果。

那只说懒加载,天猫首页精选做了什么呢? 猜猜看。

  • 图片懒加载

    • 图片是native做过缓存的。

    • 在可视区域出现才会加载

  • 卡片预先加载(懒加载的时机改变)

    • 并不是进入可视区域才加载卡片的。而是当上一张卡片进入可视区域就预先加载下一张卡片。 因为相对于图片,加载卡片UI会快得多。 这也是无痕浏览的保障之一。

  • 动画懒加载

    • 假如你快速的进行划屏滚动,List滚动高度发生很大变化,那请求数据最终还是会敌不过你的高速滑动。也就是说,在没有新的数据之前你看不到下一张卡片了,这是你必须等待了。这时候就会有一个loading的动画显示,接着等拿到了新数据,新卡片就会出现并且自动完全滑入可视区域。

    • 这里会有人说了,IOS的阻尼本来就会使得动画、滚动效果更加顺畅。在这里为想说的是,Android也一样可以。

2. 缓存

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

回到刚才懒加载提到的那个问题,为什么你要快速划屏一段时间才会遇见没有新数据的情况?原因就是缓存的数据已经用完了,所以只能让服务器给予最新的数据。

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

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

    那么,如何通过 Native 进行接口缓存呢?我们可以借助 SDK 封装来实现,即修改原来的数据接口请求方法,实现类似 Axios 的请求方法。具体来说就是,把包括 post、Get 和 Request 功能的接口,封装进 SDK 中。

    这样,客户端发起请求时,程序会调用 SDK.axios 方法,WebView 会拦截这个请求,去查看 App 本地是否有数据缓存,如果有的话,就走接口缓存,如果没有的话,先向服务端请求数据接口,获取接口数据后存放到 App 缓存中。

  • 静态资源缓存先看图。

787a8e2818e741109d68ecd800e159be_tplv-k3u1fbpfcp-watermark.image

91 requests,113 kB transferred, 2.2 MB resources,Finish: 2.93 s,DOMContentLoaded: 177 ms.2.2M的资源,达到秒开。看看Size那一列,你就应该好像领悟到什么了。

没错。HTTP缓存。 数据接口的请求一般来说较少,只有几个,而静态资源(如 JS、CSS、图片和字体等)的请求就太多了。以天猫首页为例,91 个请求中除了少数script外,其余都是静态资源请求。

那么,如何做静态缓存方案呢?这里有两种情况,一种是静态资源长期不需要修改,还有一种是静态资源修改频繁的。你可以尝试多刷新几次页面看看。

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

至于第二种,如果资源本身随时会发生改动的,可以通过设置 Etag 实现协商缓存。具体来说,在初次请求资源时,设置 Etag(比如使用资源的 md5 作为 Etag),并且返回 200 的状态码,之后请求时带上 If-none-match 字段,来询问服务器当前版本是否可用。如果服务端数据没有变化,会返回一个 304 的状态码给客户端,告诉客户端不需要请求数据,直接使用之前缓存的数据即可。当然,这里还涉及 WebView相关的东西,先不细讲。。。

3. 离线化处理

离线化是指线上实时变动的资源数据静态化到本地,访问时走的是本地文件的方案。

离线包就是一是离线化的一种方案,是将静态资源存储到 App 本地的方案,这里先不细讲。

但更复杂的另一种离线化方案:把页面内容静态化到本地。离线化一般适合首页或者列表页等不需要登录页面的场景,同时能够支持 SEO 功能。

那么,如何实现离线化呢?在打包构建时预渲染页面,前端请求落到 index.html 上时,已经是渲染过的内容。此时,可以通过 Webpack 的 prerender-spa-plugin 来实现预渲染,进而实现离线化。

Webpack 实现预渲染的代码示例如下:

// webpack.conf.js
var path = require('path')
var PrerenderSpaPlugin = require('prerender-spa-plugin')
module.exports = {
  // ...
  plugins: [
    new PrerenderSpaPlugin(
      // 编译后的html需要存放的路径
      path.join(__dirname, '../dist'),
      // 列出哪些路由需要预渲染
      [ '/', '/about', '/contact' ]
    )
  ]
}
// 面试的时候离线化能讲到这,往往就是做死现场,但风险和收益成正比,值得冒险。那就是,你有木有自己的预渲染方案。

4. 并行化

如果说懒加载、缓存和离线化都是在请求本身搞事情,想尽办法减少请求或者推迟请求,那并行化则是在请求通道上优化问题,解决请求阻塞问题,进而减少首屏时间。

例如广州打疫苗排队,新闻上报道是如何如何阻塞。 那除了让群众错开打疫苗的时间,还可以增加打疫苗的医生数量。我们在处理请求阻塞时,也可以加大请求通道数量——借助于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 文件合并了。这里提一个保留问题,用过阿里系的Antd组件库吧?库每次更新都不是全部更新,可能这次只更新一个Button组件,再次只更新一个Card组件。那是如何做到单独组件单独发版的呢?

为了解决静态域名阻塞(这是个性能瓶颈点),需要将静态域名分为 pic0-pic5,这样能提升请求并行能力。 虽然通过静态资源域名散列的办法解决了问题,但DNS 解析时间会变长很多,同时还需要额外的服务器来满足。HTTP 2.0 多路复用解决了这个问题。


原文链接:https://juejin.cn/post/6970987477133705252






扫描二维码推送至手机访问。

版权声明:本文由放牧的风发布,如需转载请注明出处。

本文链接:https://faka.grazingwind.com/post/67.html

分享给朋友:

相关文章

三十分钟包会——正则表达式

三十分钟包会——正则表达式

一、前言正则表达式,对大家来说既熟悉又陌生。熟悉是因为工作中有很多场景能用到,比如手机号、邮箱、密码等规则校验。陌生则是因为正则表达式看上去就是一堆乱码,且一眼看上去很难看懂匹配规则。有时候在网上去找一个特定规则的正则表达式,搜出来的结果各...

如何理解HTTP响应的状态码?

如何理解HTTP响应的状态码?

我们知道HTTP协议是通过HTTP请求和HTTP响应来实现双向通信的。 HTTP状态码(HTTP Status Code)是用以表示Web服务器HTTP响应状态的3位数字代码,由RFC 2616规范定义。 合理的状态码不仅可以让用...

彻底理解浏览器的缓存机制

彻底理解浏览器的缓存机制

概述浏览器的缓存机制也就是我们说的HTTP缓存机制,其机制是根据HTTP报文的缓存标识进行的,所以在分析浏览器缓存机制之前,我们先使用图文简单介绍一下HTTP报文,HTTP报文分为两种:HTTP请求(Request)报文,报文格式为:请求行...

JavaScript内存管理和垃圾回收机制

像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。相反,JavaScript是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。这个“...

NPM install -save 和 -save-dev 傻傻分不清

回顾 npm install 命令我们在使用 npm install 安装模块的模块的时候 ,一般会使用下面这几种命令形式:npm install moduleName # 安装模块到项目目录下 &...

JavaScript for...of与for...in的区别

无论是for…in还是for…of语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。for…in 语句以原始插入顺序迭代对象的可枚举属性。for…of 语句遍历可迭代对象定义要迭代的数据。以下示例显示了与Array一起使用时,fo...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。