有没有人一被问前端性能优化就慌?
总觉得自己知道点,但说出来东一榔头西一棒子,根本抓不住重点!
其实不是优化难,是没找对拆解的方法,今天就把我整理的实战优化思路分享给你,看完下次面试直接能用上!
一、 vue 方向
v-if和v-show
v-if会导致重绘重排, 从DOM树中删除、成本很大; 适应于初始渲染后续不在变化的DOM;v-show控制的是DOM样式, 避免了重绘重排, 适用频繁切换显示、隐藏 的DOM;
v-for中一定要用key
v-for渲染列表时,遵循就地复用策略;他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;- 不要使用
index索引作为key,index会改变; 尽量使用数据唯一值;
v-for和v-if不要一起使用
v-for的优先级高于v-if; 即先执行完v-for才会执行v-if逻辑, 会增加 DOM渲染难度;- 用
template包一层代替;
computed计算属性代替watch
computed计算属性的值有缓存,只有它依赖的属性值发生改变,才会触发获取逻辑;
- 路由懒加载 以及 KeepAlive
- 避免进入首页就加载全部的前端资源造成白屏时间过长;
- 静态页面(引导页、协议、介绍等页面)可设置
KeepAlive包裹,缓存起来;
- 事件的销毁 - 释放内存
addEventListener要有对应的removeEventListener销毁setTimeout\setInterval要有对应的clearTimeout\clearInterval
vue长列表性能优化
Vue会对数据进行劫持,实现双向数据绑定,但有的时候就说纯粹的数据展示,所以应避免vue静态数据进行劫持,Object.freeze方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了
- 图片懒加载 以及 图片预加载的
- 用时才开始加载,不用不加载;图片懒加载适用于 大批量图片展示的场景;
- 提前加载下次所需图片,图片预加载能够使得用户在浏览时不会出现图片加载一半导致浏览不流畅的情况;
- 插件的按需引入以及取舍(你是否真的需要)
- 比如
UI库, 往往都是比较大的,很占资源,应当使用按需引入; - 比如工具类
loadsh, 往往你只是需要一个 防抖、节流,这时自己写一个也很简单,就没必要引入了;
- 服务端渲染 和 预渲染
- 服务端渲染
SSR (vue-server-renderer)由服务端帮你渲染完成直接返回给浏览器,提高了用户体验,能快速的浏览的所需页面、SEO友好;减轻了浏览器压力,但相应的造成了服务器的压力; - 预渲染
(prerender-spa-plugin)利用了Puppeteer的爬取页面的功能,在Webpack构建阶段的最后,会本地启动一个Puppeteer的服务,访问配置了预渲染的路由,然后将Puppeteer中渲染的页面输出到 HTML 文件中,并建立路由对应的目录;
二、 react 方向
性能主要耗费在于update阶段的diff算法,因此性能优化也主要针对diff算法
减少diff算法触发次数(实际上减少update流程的次数);
注: 父组件的render必然会触发子组件进入update阶段(无论props是否更新)。
- 合并
setState
setState机制是批更新策略,已经降低了update过程的触发次数;- 尽量无论数据处理多么复杂,保证最后只调用一次
setState, 合并setState的调用;
memo
memo会对state和prop进行浅比较控制是否刷新;memo会缓存组件本身,站在全局的角度进行优化, 类似PureComponent、shouldComponentUpdate
- 适当使用
useCallback
useCallback缓存的是一个函数,是对一个单独的props值进行缓存, 返回上一次的函数引用, 可以保证依赖的值未发生改变的时候,不触发函数引用的改变;- 在向子组件传递函数
props时,每次render都会创建新函数,导致子组件不必要的渲染; useCallback可以保证,无论render多少次,我们的函数都是同一个函数,减小不断创建的开销;- 但是给所有钩子都用
useCallback包裹, 是不对的,因为多数情况下无效,还导致代码可读性变差;需结合memo配套使用;
useMemo
useMemo缓存的是一个值,可以保证依赖的值未发生改变的时候,不触发值改变;- 会根据依赖的值计算出结果,当依赖的值未发生改变的时候,不触发状态改变;
- 会在渲染的时候执行, 不是渲染之后执行, 不建议有副作用相关的逻辑;
- 类组件
PureComponent / shouldComponentUpdate
shouldComponentUpdate的返回值用于判断React组件的输出是否受当前state或props更改的影响,当props或state发生变化时,shouldComponentUpdate会在渲染执行之前被调用;PureComponent / shouldComponentUpdate; 会进行props和state的浅比较来判断组件是否需要更新( 浅比较:只会比较到两个对象的ownProperty是否符合Object.is() ,不会递归地去深层次比较);
- 慎用
forceUpdate
forceUpdate会强制更新页面,直接进入componentWillUpdate阶段,且无法拦截, 跳过优化手段(shouldComponentUpdate), 直接进入render; 建议少用;
- 正确使用 diff 算法 - 状态更新
- 不使用跨层级移动节点的操作
- 对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点;
- 尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题;
- 其他
- 图片的
懒加载、预加载、插件的按需引入和vue相同原理
三、 webpack / vite 方向
vite 自身已经做了很大程度的优化,所以主要的还是 webpack 方向;
- 生产环境关闭
sourceMap
SourceMap建立错误-代码之间的映射,方便代码调试; 适用于开发阶段;SourceMap占体积大头; 关闭之后你会发现项目小了很多;
- 对图片进行压缩
image-webpack-loader插件
cdn加载框架、插件资源
vue|react|UI框架|可视化插件等;- 通过
cdn的方式在script标签中直接使用,减少打包体积,提高加载速度;
webpack-bundle-analyzer构建结果分析
webpack-bundle-analyzer打包后会生产一个本地服务,清楚的展示打包文件的包含关系和大小;- 比较大参照组件拆分思想,进行拆分;以及对应插件或者工具类按需导入;
vite对应的是rollup-plugin-visualizer插件, 功能类似
DllPlugin提取公用库
- 开发过程中,我们经常需要引入大量第三方库,这些库并不需要随时修改或调试,我们可以使用
DllPlugin和DllReferencePlugin单独构建它们,配置webpack.dll.config.js
compression-webpack-plugin开启gzip压缩
- 开启
gzip压缩可以有效压缩资源体积,压缩比率在3到10倍左右,可以大大节省服务器的网络带宽,提高资源获取的速度; - 压缩成功
Response Headers中可以看到Content-Encoding: gzip Nginx配置如下
bash
// nginx配置开启gzip压缩,nginx会根据配置情况对指定的类型文件进行压缩
gzip on; #开启或关闭gzip on off
gzip_disable "msie6"; #不使用gzip IE6
gzip_min_length 100k; #gzip压缩最小文件大小,超出进行压缩(自行调节)
gzip_buffers 4 16k; #buffer 不用修改
gzip_comp_level 8; #压缩级别:1-10,数字越大压缩的越好,时间也越长
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; # 压缩文件类型
gzip_vary off;
Happypack将loader由单进程转为多进程
webpack的缺点是单线程的,我们可以使用Happypack把任务分解给多个子进程去并发执行,大大提升打包效率;- 配置的方法是把
loader的配置转移到HappyPack中去;
HardSourceWebpackPlugin构建缓存
- 为模块提供中间缓存步骤, 能做到第二次打包速度倍数提升;
babel-loader给loader减轻负担
babel-loader允许使用Babel和webpack转译JavaScript文件;cacheDirectory指定的目录将用来缓存loader的执行结果。 后面的构建将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的Babel重新编译过程,babel-loader提速至少两倍;cache-loader也能达到相同优化目的
babel-plugin-transform-runtime / tree-shaking减少冗余代码
webpack2默认已经支持tree-shaking
speed-measure-webpack-plugin构建速度分析
- 很清楚的知道哪个模板构建花了多少秒, 针对性的优化
四、 Http 方向
DNS预解析
- 通过
Html meta标签来告知浏览器, 当前页面要做DNS预解析;
ini
ini
体验AI代码助手
代码解读
复制代码
<meta http-equiv="x-dns-prefetch-control" content="on" />
- 页面
header中使用link标签来强制对DNS预解析
ini
ini
体验AI代码助手
代码解读
复制代码
<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />
- 使用
HTTP2
-
解析速度快
- 服务器解析
HTTP1.1的请求时,必须不断地读入字节,直到遇到分隔符CRLF为止。 - 而解析
HTTP2的请求就不用这么麻烦,因为HTTP2是基于帧的协议,每个帧都有表示帧长度的字段
- 服务器解析
-
多路复用
HTTP1.1的Pipelining技术会有阻塞的问题,处理请求响应是按照顺序的,也就是后发的请求有可能被先发的阻塞住;HTTP/2的多路复用可以粗略的理解为非阻塞版的Pipelining。即可以同时通过一个HTTP连接发送多个请求,谁先响应就先处理谁,这样就充分的压榨了TCP这个全双工管道的性能;
-
首部压缩
HTTP2提供了首部压缩功能;HTTP/1.x每次请求,都会携带大量冗余头信息,浪费了很多带宽资源;
- 减少
HTTP请求数量
-
HTTP会经历4 大步骤- 客户端连接到Web服务器
- 发送
HTTP请求 - 服务器接受请求并返回
HTTP响应 - 释放连接
TCP链接;
-
HTTP请求建立和释放需要时间, 并且随着网络情况而变化,网路差花费的时间将更长; -
不阻塞情况下
异步预先请求或者合并请求;
- 使用 http缓存
-
充分利用好
http缓存能有效减轻服务器、浏览器压力(需要后台配合); -
原理:
http缓存都是在第二次请求开始的,第一次服务器会在资源返回的响应中携带上四个常用的响应头,浏览器会通过判别这些响应值来决定资源缓存的状态,再次请求的时候浏览器会带上这些响应头;-
Cache-Control(强缓存)- 可以携带多个响应值,这些值可以设置缓存时间、状态以及验证状态;
public: 所有内容都将被缓存包括客户端、代理cdn节点private: 只缓存到客户端,不缓存到代理服务器no-cache: 需要先与服务器确认no-store: 所有内容都不被缓存max-age: 在多少秒之后失效
-
Expires(强缓存)- 标记了数据的过期时间,超过其中规定的时间后,缓存会被定义为过期,优先级
Cache-Control的max-age > Expires
- 标记了数据的过期时间,超过其中规定的时间后,缓存会被定义为过期,优先级
-
ETag(协商缓存)--> 值是一个字符串(数据的哈希值),每个数据都有一个单独的标志- 浏览器会在后续的请求中携带上这个参数来确定缓存是否需要更新;
- 需要注意的是,
ETag只有在本地缓存已过期(Expires)或者缓存模式设置为no-cache(Cache-Control)的时候,才会被浏览器携带上服务器端的值进行判别;
-
Last-Modified(协商缓存)- 向浏览器发送一个数据上次被修改的时间;
- 浏览器就知道了该数据最后被修改的时间,后续请求中,会和服务器进行时间的比较,如果服务器上的时间比本地时间要新,说明数据有更改,浏览器需要重新下载数据;
- 缺点:当服务器响应中有
Expires或者Cache-Control设置了max-age响应头的时候,浏览器不会向服务器发起校验请求,而是直接复用本地缓存。如果此时服务器进行了资源的更新,用户就无法获取到最新的资源,只能通过强制刷新浏览器缓存来跟服务器请求最新的资源
-
缓存的位置按照获取资源请求优先级,缓存位置依次如下:
Memory Cache(内存缓存) 是浏览器最先尝试命中的缓存,也是响应最快的缓存。但是存活时间最短的,当进程结束后,tab 标签关闭后,缓存就不存在了,因为内存空间比较小,通常较小的资源放在内存缓存中,比如 base64 图片等资源Service Worker(离线缓存) Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。Disk Cache(磁盘缓存) 内存的优先性,导致大文件不能缓存到内存中,那么磁盘缓存则不同。虽然存储效率比内存缓存慢,但是存储容量和存储市场有优势。- Push Cache(推送缓存)它是最后一道缓存
-
- 优先使用get请求
get请求不需要预检和交互; 频繁刷新浏览器不会对浏览器、服务器造成太大的压力,无伤;
- 使用
CDN服务器端缓存加快访问速度 (俗称边缘计算)
-
原理:
CDN网络是在用户和服务器之间增加了一层缓存层,将用户的请求引导到最优的缓存节点就近获取所需要的内容而不是服务器源站,从而降低网络用塞、加块访问速度响应用户的请求 (俗称负载均衡); -
过程:先向
CDN边缘节点发起请求 -> 检测是否过期 -> 没有直接返回 -> 过期则去根服务器获取数据再返回; -
设置:通过
http响应头中的Cache-Control和max-age的字段来设置CDN边缘节点的数据缓存时间; -
例子:网站中大量的
css,html,js等文件、大文件的下载(图片、视频、音频等),将这些静态内容推送到CDN节点; -
构成:初始服务器,分布于各个节点的缓存服务器,重定向DNS服务器和内容交换服务器
-
主要技术:
- 负载均衡;
- 内容存储技术 (内容源的存储、内容在
cache节点中的分布式存储); - 内容分发技术(构建网络,将链接到
IP网络上的内容,快速的传输到用户终端)
-
缺点:当源服务器资源更新后,如果
CDN节点上缓存数据还未过期,用户访问到的依旧是过期的缓存资源,这会导致用户最终访问出现偏差。因此,开发者需要手动刷新相关资源,使CDN缓存保持为最新的状态
- 减少
DNS查找次数
- DNS用于映射主机名和
IP地址,DNS解析有代价,一般一次解析需要20~120毫秒。浏览器在DNS查询完成前不会下载任何东西,所以浏览器会想办法对DNS的查找结果进行缓存 - 减少域名主机可减少DNS查询的次数,最理想的方法就是将所有的内容资源都放在同一个域(
Domain)下面,这样访问整个网站就只需要进行一次DNS查找,这样可以提高性能。 - 在
HTTP /1.1中放在同个域下面会带来一定数量的并行度(它的建议是2),那么就会出现下载资源时的排队现象,这样就会降低性能,推荐客户端针对每个域在一个网站里面使用至少2个域,但不多于4个域
- 请求返回体的压缩、分页、缓存
- 当一个请求返回数据比较多(如数据字典、图片、execl表格、pdf、音频、视频)时;优先和后台协商使用
缓存、分页、切片等方案; - 并且
异步请求放到请求队尾, 当接收数据很大时,你会发现浏览器会进入卡死状态;
四、 图片方向
- 优先使用
雪碧图
- 图片、图标的切换优先使用雪碧图代替、以减少体积, 提高响应速度;
- 使用
font字体、svg、base64、JPG、JPEG、WEBP格式的图片 - 列表图片使用预加载、懒加载、及脱离文档流后进行
DOM回收
- 大批量图片渲染是很耗浏览器性能的,并且会阻塞其他渲染,这时肯定需要使用预加载、懒加载方法;
- 对图片进行操作时,尽量脱离文档流后进行
DOM回收,避免重绘重排;
- 使用
http、cdn缓存、不失帧情况下对图片压缩
- 缓存、压缩目的都是快速响应用户操作显示图片.
五、浏览器渲染方向
SSR(优化首页渲染时间)、骨架屏、开启gzip压缩、js混淆(无效字符及注释的删除、码语义的缩减和优化)css的文件放在头部、css压缩、合并css资源- 减少重定向、减少外链、不滥用
web字体、,js文件放在尾部或者异步(async和defer、动态脚本创建) ( 标签preload渲染前加载,prefetch,dns-prefetch渲染完成后空闲时间加载 ) - 避免內联样式、避免
html里执行js, 多次修改样式、结构,尽量合并在一起修改; - 使用
css动画、减少css表达式、使用requestAnimationFrame操作动画 - 避免重绘重排、减少
DOM元素个数 - 批量操作
DOM,脱离文档流后在操作; - 使用
css3 GPU硬件加速
translate3d、translateZ、rotate、scale、transform、opacity、filters等动画效果不会引起回流重绘
- 对于频繁操作使用节流、防抖
- 节流:短时间内大量触发同一事件,在函数执行一次之后,该函数在指定的时间期限内不再执行,直至过了这段时间才重新生效(例如监听滚动条
scroll事件); - 防抖:如果短时间内大量触发同一事件,只会执行一次函数(例如:
input事件);
- 长列表优化
vue|raect对应的UI框架基本都提供了虚拟列表组件, 优先使用虚拟列表组件;
- 使用
JSON格式
JSON是一种轻量级的数据交换格式,是理想的数据交换格式。同时,JSON是JavaScript原生格式,这意味着在JavaScript中处理JSON数据不需要任何特殊的API或工具包
- 控制
Cookie大小和污染
Cookie是本地的磁盘文件,每次浏览器都会去读取相应的Cookie,所以建议去除不必要的Coockie,使Coockie体积尽量小- 使用
Cookie跨域操作时注意在适应级别的域名上设置coockie以便使子域名不受其影响 Cookie是有生命周期的,所以请注意设置合理的过期时间,合理地Expire时间和不要过早去清除coockie
- 其他
- 避免
404、减少DOM 访问、用<link>代替@import、保持单个内容小于25K
六、 Chrome Performance 分析
多使用 Chrome Performance 的火焰图 查找性能瓶颈,针对性的优化;
- 评测报告中
FP、FCP、FMP、LCP、TTI、TTFB、FCI、FID、DCL、Speed Index
FP"首次绘制" 是第一个"时间点",它代表浏览器第一次向屏幕传输像素的时间,就是页面在屏幕上首次发生视觉变化的时间。FCP"首次内容绘制" , 代表浏览器第一次向屏幕绘制 "内容" (只有首次绘制文本、图片(包含背景图)、非白色的canvas或SVG时才被算作FCP)FP和FCP可能是相同的时间,也可能是先FP后FCP。FMP"首次有效绘制" 主要内容"开始出现在屏幕上的时间点。它是我们测量用户加载体验的主要指标LCP可视区"内容"最大的可见元素开始出现在屏幕上的时间点。TTI"可交互时间" 网页第一次 完全达到可交互状态 的时间点TTFB表示浏览器接收第一个字节的时间FCI告诉我们页面什么时候完全达到可用FIDFID指的是用户首次与产品进行交互时,我们产品可以在多长时间给出反馈DCLDomContentloaded事件触发的时间Speed Index页面可见部分的平均时间- 商城、官网、博客这种页面更侧重FMP(用户希望尽快看到有价值的内容),而类似后台管理系统或在线PPT这种产品则更侧重
TTI(用户希望尽快与产品进行交互)。
- 白屏时间计算
- 将代码脚本放在
</head>前面就能获取白屏时间:
xml
<script>
new Date().getTime() - performance.timing.navigationStart
</script>
- 首屏时间计算
- 在
window.onload事件中执行以下代码,可以获取首屏时间:
vbscript
new Date().getTime() - performance.timing.navigationStart
海云前端丨前端开发丨简历面试辅导丨求职陪跑