聊聊前端性能优化

1,088 阅读12分钟

前言

对于一个有追求的前端工程师来说,前端性能优化始终是一个绕不开的话题。如果对前端性能优化有一定了解,你一定听说过 雅虎军规或者网上看到过很多关于前端性能优化的文章;然而无论是雅虎军规还是网络上关于前端性能优化的文章,你会发现前端性能优化手段众多且场景众多,一时间眼花缭乱没个抓手。本文整理部分场景以及应用手段并归类,希望给大家一个思考方向。

1 两个大方向

前端性能优化实际上要面对的是一个复杂的场景,有针对网络加载性能的,有针对用户交互体验的,有针对研发体验的;这些场景既相互独立又相互影响,而针对这些场景的优化方案多且乱,想要在这杂乱的方案中选取比较好的方案并且最终落地到自己的项目上,则需要对整个优化体系要有个清晰的理解。

虽然我们面对的场景复杂,优化方案多而杂,但是最终所有的优化手段都会在两大方向落地:

  • 代码优化
  • 网络优化

1.1 代码优化

先来说说代码优化代码优化 又可以分为 编译时优化运行时优化

1.1.1 编译时优化

先来看看 编译的四个步骤:

1、加载目标文件(I/O操作非常耗时)。
2、解析文件生成AST。
3、代码转换,比如:将 JSX / TS转换为 JS, ES Next 转换为 es5,less / scss 转换为 css。
4、生成目标代码,比如:CMD、AMD、CommonJS、压缩、混淆等

编译时优化 目的是减少 I/O代码转换目标代码输出三个步骤的时间,提高构建效率,提升研效;这里以构建工具为例。

说到构建工具就不得不提webpackwebpack在这方面做了很多努力,如:

1、合理配置resolvemodulesextensionsmainFilesalias缩小文件的搜索范围。
2、loader 通过 testexcludeinclude配置缩小文件的搜索范围。
3、使用DllPlugin减少依赖库的编译,只需编译一次,只要版本不升级就不需要再次编译。
4、使用HappyPack开启多线程打包。
5、其他的还有缓存、 优化压缩时间等。

可以看到 webpack 已经很努力了,但还有一个问题,在项目运行时 webpack 会把整个项目都编译一遍,这对于越来越大的前端项目来说,项目启动时间无疑是非常耗时的。

有没有可能只编译开发只关注的页面的相关代码呢?

vite 给出了他的解决方案;vite 解决这个问题的核心思想是:

1、按需编译;vite启动时不会编译代码,只提供服务,只有服务端接收到文件请求时才编译。
2、核心是依托支持 ES Module 的浏览器来真正触发需要加载的模块,然后本地服务再加载对应的模块文件并即时编译为js返回给浏览器。

这里的 编译优化 问题, vite 使用新一代构建工具 ESBuild 来解决的。

ESBuild构建速度非常快,原因主要以下几点:

1、它是用 Go 语言编写的,并可以编译为本地代码,这意味着它在开始工作的时候可以直接工作,而不像其他javascript 构建工具那样还需要js引擎解析构建工具本身。
2、编译过程大量使用并行。
3、所有模块都是自己实现,方便优化算法,提高内存、cpu等利用。

除了编译快,vite 还使用了缓存减少不必要的编译,这就极大的提升了开发体验。

通过 webpackviteESBuild可以看到,编译时优化 核心方向是:

1、减少文件查询读取(I/O)时间;
2、提升编译效率;
3、减少不必要的编译;
4、算法优化,提升cpu、内存等的利用。

当然前端构建工具还有很多比如:ParcelgulpRollupSnowpackRomeMicrobundle等,篇幅有限就不多说了。

1.1.2 运行时优化

运行时 可以考虑的点就比较多了,我们以浏览器 渲染管线 为例,

渲染管线 的核心工作是:

1、解析html、css生成dom树和样式层级表,过程中遇到<script>则解析并执行js
2、计算出真正需要渲染的节点以及其元素大小、位置(这里计算耗费巨大)
3、分层,一些节点可能需要单独的图层
4、绘制,生成绘制指令表
5、提交给合成线程

可以看到,渲染进程 核心工作涵盖html、css、js,所以 运行时优化 主要考虑以下几点:

1、算法运行时效率,由于js运行时是单线程,如果程序长时间占用线程执行没法响应用户行为,体验会非常差;
2、精简的代码,比如:打包编译时使用Tree Shaking,减少运行时浏览器解析编译时间;
3、合理的代码,比如:尽量使用函数式编程范式进行编程,可以让引擎比如v8复用已经编译好的代码;
4、dom操作优化,css 优化等,减少重绘、重排等需要重新计算和绘制但可能又非必要的操作。

针对这4个优化点我们以当下主流的框架 VueReact 来看看优化手段。

1、相同点

1、都是数据驱动,使用了vnodediff
2、Reactvue3 都迈向 Hooks,优化逻辑复用

2、不同点 Vue2Vue3

1、数据劫持优化,Object.defineProperty 改为 Proxy,减少无脑递归 2、渲染优化,Vue3在编译阶段对模板进行分析,将静态节点生成 Block tree,减少diff范围并且vnode更新可由模块减少为动态内容数量大小 3、Options APIComposition API,提升逻辑处理能力

React

1、用户可以利用生命钩子自定义diff策略;
2、React 16引入 Fiber,核心理念是利用帧空余调度任务执行,适时释放渲染进程占用,同时使用启发式更新算法,可以指定任务更新优先级;
3、事件监听优化,旧版React会把事件监听代理到 document,这会导致页面的事件都会触发事件回调,导致 渲染管线 的流程走一遍,React 17 则是代理到渲染树根DOM中;
4、React 17 优化启发式更新算法,简单讲就是设定一个更新区间,并且动态的向区间中增减优先级,这样可以处理更细颗粒度的更新;
5、React 18并发渲染机制(Concurrent Rendering),主要是调度流程的优化

1.1.3 小结

总结一下,代码优化的两个核心场景是 编译时优化运行时优化;通过构建工具优化和框架优化对比,我们可以看到两个场景优化的落脚点主要有:I/O操作缓存减少dom操作算法优化,其中算法优化贯穿整个优化过程。当然优化是无止尽的,根据优化的落脚点以及当下比较成熟的工具和框架的案例可以举一反三,针对自己的实际应用场景做优化。

1.2 网络优化

代码优化 是研发人员相对可控的一个方向,而 网络优化 则相对是不太可控的一个方向,只能根据协议和硬件设施的情况选择综合效益最大的优化;网络优化 主要考虑有以下几点:

  1. 寻址:dns 花费时间从十几毫秒到上百毫秒不等;
  2. 静态文件访问速率:静态文件都可以缓存在 CDN 上,不需要每次都从源站获取;
  3. 传输效率:不同协议以及协议版本对带宽的利用率不同;
  4. 代理服务器:可以处理协议转换,短时缓存,加速访问等问题;
  5. 负载均衡:防止单点压力过大导致崩溃。

这么多个优化点,我们可以理解为两个方向:一个是负责内容传输的,一个是负责内容生产;对应的可以参考OSI七层模型去思考,这里就将 网络优化 理解为如下两个方向:

  • 网络传输协议,主要以HTTP协议为例
  • Server 端,以NginxDNSCDN为例

1.2.1 网络传输协议

网络传输协议 顾名思义就是可以使文本、视频、声音等信息在网络传递,主要是解决内容传输的问题。

超文本传输协议-HTTP协议为例,HTTP可以使文本、视频、声音等信息在网络中高效的传递。先来简单看下其各个版本的问题都是如何被后续版本优化的:

  1. HTTP/1.1发明以来,网络中信息的数量以及大小成倍增加,从简单文本到富媒体,实时性要求增多,而HTTP/1.1同时只能完成一个HTTP事务,并发连接有限,巨大的HTTP头部,即使带宽增加也无法带来延迟降低;
  2. HTTP/2.0在应用层上修改,基于并充分挖掘 TCP 协议性能,其有以下主要特性:以二进制方式传输、标头压缩(HPACK、Huffman 编码)、多路复用(多个 Stream 共享 TCP 层的流量控制)、服务器消息推送;但是HTTP/2.0也有问题,如:多路复用与 TCP 的队头阻塞问题、TCP 以及 TCP+TLS 建链握手过多的问题;
  3. HTTP/3。0则是以 UDP + QUIC 设计,有以下特性:UDP没有队列概念(解决了队头阻塞问题)、1RTT 完全握手(客户端生成所有握手信息直接给服务端)、0RTT 恢复会话握手(复用会话,之前的会话可以直接复用)

HTTP协议迭代过程以及其解决的问题可以看到,核心是

  1. 减少不必要的传输以及降低传输内容大小;
  2. 减少不必要的握手以及复用握手;
  3. 传输内容从有序到相对无序,解决阻塞问题

1.2.2 Server 端

网络传输协议是负责内容传输的而 Server 端则是负责内容的生产、存储,性能主要体现在访问速率上。这里从以下几个方向来看看:

DNS

  • 减少DNS的请求次数
  • 进行DNS预获取:DNS Prefetch
  • 延长DNS缓存时间
  • 使用CDN加速域名

CDN

  • 提升访问速度:CDN网络还不受运营商相互访问较慢的限制,还缓存了很多静态资源,此外,还优化了数据传输路径
  • 安全稳定:CDN中的负载均衡设备可以维持网站各个节点的平衡,防止单点崩溃
  • 低成本:不需要到源站请求,可以减少带宽
  • SEO优化:搜索引擎更快解析到

Nginx

  • Nginx运行进程数量
  • CPU亲和力配置
  • 最大打开文件数
  • 开启高效传输模式
  • 配置合理的连接超时时间
  • fastcgi 调优
  • gzip 调优,视频类不需要压缩
  • expires 缓存调优
  • 七层负载均衡
  • 编写或使用社区高性能模块
  • 缓存等

DNSCDN的优化来看,核心就三个字:稳定

Nginx的优化来看则要考虑硬件本身,核心就四个字:合理高效

1.2.3 小结

总结一下 网络优化核心是网络传输协议Server 端两个方向即内容传输和内容生产,其过程要与硬件、地理空间打交道,其中不可控因素较多,要保证的前提又要稳定,而要保证稳定的体验又要相应技术能合理利用硬件资源同时保持高效运作。

综合考虑

从以上分析可以看出围绕代码优化网络优化两个大方向衍生出多种技术治理手段,但是并不是所有手段都能发挥到极致,根据不同情况需要做取舍,最重要的是合理。

比如使用VueReact在操作dom上并不比jQuery快,但是VueReact解决的是代码规模变大后的治理问题。

比如SSR并不是返回的数据越多越好,一般来讲只需要首页即可。

再比如 Nginx 运行工作进程数量不是越多越好,而是根据cup核数来制定。

总之性能优化是要让各种资源利用率最大化,围绕这个来展开是基本不会错的,到这里技术方案已经没有一定的优劣,优化方案一定是综合了各个方面的考虑来选取的,否则经不起推敲。

总结

前端性能优化涵盖了从URL输入到页面呈现的全过程,目前的优化手段就已经非常丰富了,随着技术的发展迭代,往后的优化手段更是层出不穷,如何快速理解技术和问题的本质,则需要前端性能优化有个全面的认知,本文将前端性能优化拆解为代码优化网络优化两个大方向,在代码优化方向上再分出编译时优化运行时优化两个具体方向,在网络优化方向上再分出网络传输协议Server 端 负责内容传输和内容生产方向,以更好的区分技术治理手段落地点,这样在遇到问题时可以快速的查找相应技术手段或者创造技术手段,同时任何一种技术都不是万能的,必须综合所有情况调整到一个合理的状态才能发挥最大效能。