面试中性能优化到底怎么答?

面试官想考察的是什么呢?

问性能优化问题的面试官大多都是处于以下几点目的:

  • 面试者是否有做过前端性能优化方面的实践。
  • 面试者是否有系统的性能优化知识。

我们应该如何回答?

一般面试官的问题大致分为两种情况:

  • 你在XX项目中做过哪些性能优化?(具体实现
  • 你如何对一个项目进行性能优化,或者说说你对前端性能优化的看法?(整体理解,系统优化

具体实现

  1. 性能问题的出现

    在今年的xx月,测试同事发现在这个项目的xx页面加载的时候出现卡顿

    这点其实能编,注意 对于产品、测试、用户 而言,能直观感受到的就是卡顿、慢

  2. 问题复现:随后我打开页面,通过工具测试发现几个性能指标存在问题FCPTTI这两个性能指标都过长。

    这里的工具可以是performancelighthouse前端埋点SDK亦或者其他第三方的监测工具

    比如 : FCP达到了3.x秒,TTI`更是长达5.x秒

  3. 问题分析:我发现在xx页面加载的时候会先获取几张比较大的图片,导致FCP指标过长。

    分析过程相信大家都有,这段大家可以自己想想,在此我以FCP为例

  4. 优化方案:采取了图片优化策略xxx执行优化…

    下文提及哪些优化策略

  5. 量化优化效果:在经过上述的优化方案后,我们最终将FCP优化到了1.8秒,TTI优化到了3.8秒。

    量化你的优化成果

  6. 优化是否达标:如果公司对性能指标的数据有强要求,比如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

具体的操作与ViteWebpack等工具是强耦合的

  1. 缩小加载范围配置include/exclude缩小Loader对文件的搜索范围,好处是避免不必要的转译。node_modules目录的体积这么大,那得增加多少时间成本去检索所有文件啊?

  2. 打包缓存:很多工具都可以开启打包的缓存,这一步能大大减少构建时间。配置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 })
    ]
    };

  3. 并行构建配置Thread将Loader单进程转换为多进程,好处是释放CPU多核并发的优势。在使用webpack构建项目时会有大量文件需解析和处理,构建过程是计算密集型的操作,随着文件增多会使构建过程变得越慢。

    诸如happyPackthread-loader 等工具都可以在不同阶段开启CPU多核进行并行构建,大大提升开发时效率。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import Os from "os";

    export default {
    // ...
    module: {
    rules: [{
    // ...
    test: /\.js$/,
    use: [{
    loader: "thread-loader",
    options: { workers: Os.cpus().length }
    }, {
    loader: "babel-loader",
    options: { cacheDirectory: true }
    }]
    }]
    }
    };
  4. 可视化分析配置BundleAnalyzer分析打包文件结构,好处是找出导致体积过大的原因。从而通过分析原因得出优化方案减少构建时间。BundleAnalyzerwebpack官方插件,可直观分析打包文件的模块组成部分、模块体积占比、模块包含关系、模块依赖关系、文件是否重复、压缩体积对比等可视化数据。

    Viterollup-plugin-visualizer、和Webpackwebpack-bundle-analyzer

1
2
3
4
5
6
7
8
9
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";

export default {
// ...
plugins: [
// ...
BundleAnalyzerPlugin()
]
};
  1. 分割代码 : 分割各个模块代码,提取相同部分代码,好处是减少重复代码的出现频率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default {
// ...
optimization: {
runtimeChunk: { name: "manifest" }, // 抽离WebpackRuntime函数
splitChunks: {
cacheGroups: {
common: {
minChunks: 2,
name: "common",
priority: 5,
reuseExistingChunk: true, // 重用已存在代码块
test: AbsPath("src")
},
vendor: {
chunks: "initial", // 代码分割类型
name: "vendor", // 代码块名称
priority: 10, // 优先级
test: /node_modules/ // 校验文件正则表达式
}
}, // 缓存组
chunks: "all" // 代码分割类型:all全部模块,async异步模块,initial入口模块
} // 代码块分割
}
};

网络加载的性能优化

首先,加载层面就是网络在加载文件,核心要点就是

怎么快呢 ==> 文件小 / 网络快 / 缓存

image-20240409112143461

构建策略:减小文件体积:(其实很多也是通过配置字段)

  • 代码分割Split Chunk (上面已经说过)
  • **Tree Shaking**:其实大部分工具已经自带了
  • 动态垫片通过垫片服务根据UserAgent返回当前浏览器代码垫片,好处是无需将繁重的代码垫片打包进去。每次构建都配置@babel/preset-envcore-js根据某些需求将Polyfill打包进来,这无疑又为代码体积增加了贡献

浏览器代码垫片(Browser polyfill)是一种用于 Web 开发的技术,它允许开发者在旧版本的 Web 浏览器上使用新的 Web 技术和 API。在 Web 开发中,新的 Web 技术和 API 通常会首先在较新的浏览器中得到支持,但对于那些仍在使用旧版本浏览器的用户,这些新特性就无法正常工作。为了解决这个问题,开发者可以使用代码垫片来模拟这些新特性,以便在旧版本的浏览器上实现类似的功能。

  • 按需加载:使用的时候才加载 dynamic-import

  • 压缩资源压缩HTML/CSS/JS代码,压缩字体/图像/音频/视频,好处是更有效减少打包体积

  • 图像处理:大多数情况下,对图片进行优化的成效往往是巨大的。

网络策略:CDNCDN即时内容分发网络。使用CDN可降低网络拥塞,提高用户访问响应速度和命中率。其核心特征是缓存回源,缓存是把资源复制到CDN服务器里,回源是资源过期/不存在就向上层服务器请求并复制到CDN服务器里。

缓存策略:强缓存、协商缓存:这也是非常常见的浏览器缓存方案。

image-20240409133004324 image-20240409133012365
渲染层面的性能优化
image-20240409113400904

CSS策略:

  • 避免出现多层的嵌套规则
  • 避免为ID选择器添加多余选择器
  • 避免使用通配选择器,只对目标节点声明规则
  • 避免重复匹配重复定义,关注可继承属性(这点不强要求)

DOM策略:(回流重绘)

  • 缓存DOM计算属性
  • 避免过多DOM操作
  • 使用DOMFragment缓存批量化DOM操作

阻塞策略:

  • 脚本与DOM/其它脚本的依赖关系很强:对<script>设置defer
  • 脚本与DOM/其它脚本的依赖关系不强:对<script>设置async

回流重绘策略

  • 缓存DOM计算属性
  • 使用类合并样式,避免逐条改变样式
  • 使用display控制DOM显隐,将DOM离线化

异步更新策略

  • 异步任务中修改DOM时把其包装成微任务

代码实践策略:

  • 防抖、节流
  • 图片懒加载
  • 绘图时可开启GPU加速
  • 时间分片、Web Worker处理大、长逻辑
  • ……

回答方式

在前端优化方面,我通常将其分为三个部分

  1. 构建和打包的性能优化
  2. 网络加载的性能优化
  3. 浏览器渲染的性能优化

构建和打包我们主要就是通过进行一些脚手架配置和相关插件来进行构建和打包的优化, 有几个策略(…….)

网络加载优化我将其分为几个策略, 构建 / 网络 / 缓存, 构建策略主要是通过减小文件体积, 有几种方法(….), 网络策略… 缓存策略

浏览器渲染的优化就落实到前端具体的技术细节了, 主要分为几种优化方向(CSS/DOM/阻塞/回流重绘/异步更新), 分别具体说

参考文章

https://juejin.cn/post/7280831651084681251?share_token=43d3ab1f-f24b-44e8-ac72-85953854052e#heading-11

https://juejin.cn/post/6981673766178783262?searchId=202404091150437D2B3549225634F6833D#heading-14


面试中性能优化到底怎么答?
http://example.com/2024/04/09/01/
作者
weirdo
发布于
2024年4月9日
更新于
2024年4月9日
许可协议