面试中性能优化到底怎么答?
面试官想考察的是什么呢?
问性能优化问题的面试官大多都是处于以下几点目的:
- 面试者是否有做过前端性能优化方面的实践。
- 面试者是否有系统的性能优化知识。
我们应该如何回答?
一般面试官的问题大致分为两种情况:
- 你在XX项目中做过哪些性能优化?(具体实现)
- 你如何对一个项目进行性能优化,或者说说你对前端性能优化的看法?(整体理解,系统优化)
具体实现
性能问题的出现:
在今年的xx月,测试同事发现在这个项目的xx页面加载的时候出现卡顿。
这点其实能编,注意 对于产品、测试、用户 而言,能直观感受到的就是卡顿、慢
问题复现:随后我打开页面,通过工具测试发现几个性能指标存在问题:
FCP
、TTI
这两个性能指标都过长。这里的工具可以是
performance
、lighthouse
、前端埋点SDK
亦或者其他第三方的监测工具
比如 : FCP
达到了3.x秒,
TTI`更是长达5.x秒问题分析:我发现在xx页面加载的时候会先获取几张比较大的图片,导致
FCP
指标过长。分析过程相信大家都有,这段大家可以自己想想,在此我以
FCP
为例优化方案:采取了图片优化策略xxx执行优化…
下文提及哪些优化策略
量化优化效果:在经过上述的优化方案后,我们最终将
FCP
优化到了1.8秒,TTI
优化到了3.8秒。量化你的优化成果
优化是否达标:如果公司对性能指标的数据有强要求,比如
FCP
必须在2秒以内诸如此类…不强求
整体理解
第一步,如何对项目进行性能分析
性能指标是衡量我们项目的性能最重要的东西,
我们常见的指标有以下这些:
load(Onload Event),它代表页面中依赖的所有资源加载完的事件。
DCL(DOMContentLoaded),DOM解析完毕。
FP(First Paint),表示渲染出第一个像素点。FP一般在HTML解析完成或者解析一部分时候触发。
FCP(First Contentful Paint),页面从开始加载到页面内容(文本、图片、背景图、svg 元素或非白色 canvas 元素)的任何部分在屏幕上完成渲染的时间,是测量加载速度感知的重要指标之一。
FMP(First Meaningful Paint),首次渲染有意义的内容的时间,“有意义”没有一个标准的定义,FMP的计算方法也很复杂(建议不使用,或者结合产品经理讨论使用)。
LCP(largest contentful Paint),页面首次开始加载的时间到可视区域内可见的最大图像或者文本块完成渲染的时间。 首屏需要重点关注这一项指标。
最大内容绘制(LCP)的元素大小是指用户在可视区域内可见的大小,所以考量都是基于可视区域为准,如果元素有延伸到可视区域外,或者元素被裁剪或包含不可见的溢出,这些部分不计入元素大小;
FID:首次输入延迟。衡量的是从用户首次与页面交互(例如点击链接、按钮等)到浏览器实际响应用户操作之间的延迟时间。
首先 : 通过performance
工具对项目页面性能进行分析,从中筛选出部分指标作为本项目的性能衡量指标
第二步,系统的优化方案
构建和打包的性能优化
主要是工程化方面的一些配置优化
![image-20240409112419518](https://weirdo-blog.oss-cn-chengdu.aliyuncs.com/blog/202404091124584.png)
具体的操作与
Vite
和Webpack
等工具是强耦合的
缩小加载范围:配置include/exclude缩小Loader对文件的搜索范围,好处是
避免不必要的转译
。node_modules目录的体积这么大,那得增加多少时间成本去检索所有文件啊?打包缓存:很多工具都可以开启打包的缓存,这一步能大大减少构建时间。配置cache缓存Loader对文件的编译副本,好处是
再次编译时只编译修改过的文件
。未修改过的文件干嘛要随着修改过的文件重新编译呢?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 大部分Loader/Plugin都会提供一个可使用编译缓存的选项,通常包含cache字眼。
// 以babel-loader和eslint-webpack-plugin为例。
import EslintPlugin from "eslint-webpack-plugin";
export default {
// ...
module: {
rules: [{
// ...
test: /\.js$/,
use: [{
loader: "babel-loader",
options: { cacheDirectory: true }
}]
}]
},
plugins: [
new EslintPlugin({ cache: true })
]
};并行构建:配置Thread将Loader单进程转换为多进程,好处是
释放CPU多核并发的优势
。在使用webpack
构建项目时会有大量文件需解析和处理,构建过程是计算密集型的操作,随着文件增多会使构建过程变得越慢。诸如
happyPack
、thread-loader
等工具都可以在不同阶段开启CPU多核进行并行构建,大大提升开发时效率。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import Os from "os";
export default {
// ...
module: {
rules: [{
// ...
test: /\.js$/,
use: [{
loader: "thread-loader",
options: { workers: Os.cpus().length }
}, {
loader: "babel-loader",
options: { cacheDirectory: true }
}]
}]
}
};可视化分析:配置BundleAnalyzer分析打包文件结构,好处是
找出导致体积过大的原因
。从而通过分析原因得出优化方案减少构建时间。BundleAnalyzer
是webpack
官方插件,可直观分析打包文件
的模块组成部分、模块体积占比、模块包含关系、模块依赖关系、文件是否重复、压缩体积对比等可视化数据。如
Vite
的rollup-plugin-visualizer
、和Webpack
的webpack-bundle-analyzer
。
1 |
|
- 分割代码 : 分割各个模块代码,提取相同部分代码,好处是
减少重复代码的出现频率
。
1 |
|
网络加载的性能优化
首先,加载层面就是网络在加载文件,核心要点就是快
怎么快呢 ==> 文件小 / 网络快 / 缓存
![image-20240409112143461](https://weirdo-blog.oss-cn-chengdu.aliyuncs.com/blog/202404091121588.png)
构建策略:减小文件体积:(其实很多也是通过配置字段)
- 代码分割:
Split Chunk
(上面已经说过) - **
Tree Shaking
**:其实大部分工具已经自带了 - 动态垫片:通过垫片服务根据UserAgent返回当前浏览器代码垫片,好处是
无需将繁重的代码垫片打包进去
。每次构建都配置@babel/preset-env
和core-js
根据某些需求将Polyfill
打包进来,这无疑又为代码体积增加了贡献
浏览器代码垫片(Browser polyfill)是一种用于 Web 开发的技术,它允许开发者在旧版本的 Web 浏览器上使用新的 Web 技术和 API。在 Web 开发中,新的 Web 技术和 API 通常会首先在较新的浏览器中得到支持,但对于那些仍在使用旧版本浏览器的用户,这些新特性就无法正常工作。为了解决这个问题,开发者可以使用代码垫片来模拟这些新特性,以便在旧版本的浏览器上实现类似的功能。
按需加载:使用的时候才加载 dynamic-import
压缩资源:压缩HTML/CSS/JS代码,压缩字体/图像/音频/视频,好处是
更有效减少打包体积
。图像处理:大多数情况下,对图片进行优化的成效往往是巨大的。
网络策略:CDN:CDN
即时内容分发网络。使用CDN
可降低网络拥塞,提高用户访问响应速度和命中率。其核心特征是缓存
和回源
,缓存是把资源复制到CDN服务器
里,回源是资源过期/不存在
就向上层服务器请求并复制到CDN服务器
里。
缓存策略:强缓存、协商缓存:这也是非常常见的浏览器缓存方案。
![image-20240409133004324](https://weirdo-blog.oss-cn-chengdu.aliyuncs.com/blog/202404091330529.png)
![image-20240409133012365](https://weirdo-blog.oss-cn-chengdu.aliyuncs.com/blog/202404091330463.png)
渲染层面的性能优化
![image-20240409113400904](https://weirdo-blog.oss-cn-chengdu.aliyuncs.com/blog/202404091134983.png)
CSS策略:
- 避免出现多层的
嵌套规则
- 避免为
ID选择器
添加多余选择器 - 避免使用
通配选择器
,只对目标节点声明规则 - 避免重复匹配重复定义,关注
可继承属性
(这点不强要求)
DOM策略:(回流重绘)
- 缓存
DOM计算属性
- 避免过多
DOM操作
- 使用
DOMFragment
缓存批量化DOM操作
阻塞策略:
- 脚本与
DOM/其它脚本
的依赖关系很强:对<script>
设置defer
- 脚本与
DOM/其它脚本
的依赖关系不强:对<script>
设置async
回流重绘策略
- 缓存
DOM计算属性
- 使用类合并样式,避免逐条改变样式
- 使用
display
控制DOM显隐
,将DOM离线化
异步更新策略
- 在
异步任务
中修改DOM
时把其包装成微任务
代码实践策略:
- 防抖、节流
- 图片懒加载
- 绘图时可开启GPU加速
- 时间分片、
Web Worker
处理大、长逻辑 - ……
回答方式
在前端优化方面,我通常将其分为三个部分:
- 构建和打包的性能优化
- 网络加载的性能优化
- 浏览器渲染的性能优化
构建和打包我们主要就是通过进行一些脚手架配置和相关插件来进行构建和打包的优化, 有几个策略(…….)
网络加载优化我将其分为几个策略, 构建 / 网络 / 缓存, 构建策略主要是通过减小文件体积, 有几种方法(….), 网络策略… 缓存策略
浏览器渲染的优化就落实到前端具体的技术细节了, 主要分为几种优化方向(CSS/DOM/阻塞/回流重绘/异步更新), 分别具体说
参考文章
https://juejin.cn/post/6981673766178783262?searchId=202404091150437D2B3549225634F6833D#heading-14