从原理到实践:一篇文章吃透前端性能优化与面试回答

172 阅读7分钟

前言

大家好,这里是3Katrina,最近作者在面试时经常遇到一类题目--页面渲染有哪些性能优化?你的项目中有哪些性能优化?你是如何评价性能的? 今天这篇文章就带大家全面梳理下性能优化,大家下次遇到这类问题时,可以参考这篇文章!😀


如何进行性能优化?/ 有哪些地方可以性能优化?

这个问题我的回答逻辑是:减少回流/重绘、优化资源加载、优化js、利用框架优化、利用缓存、优化网络、优化首屏 接下来的回答我会紧扣这七个点进行回答:

减少回流/重绘

这个点虽然是回答性能优化的一点,但其实它也能单独出一道关于回流/重绘的考题了,下面就详细说明

回答逻辑: 什么是回流重绘?-> 什么会影响回流/重绘 -> 如何减少回流/重绘?

什么是回流重绘?

重绘是指元素样式发生改变而不影响布局触发的浏览器重新绘制,具体来说:当触发重绘时,渲染过程中的渲染树回更新受影响的部分,然后进行重新执行绘制指令。

回流是指当前页面结构发生改变或者元素的样式发生改变 影响页面布局时触发的重新计算绘制,具体来说:当触发回流时,渲染过程中的布局阶段需要重新计算当前的页面布局并重新执行绘制指令。

什么会触发回流/重绘?

回流一定会触发重绘,重绘不一定会回流,所以我会回答什么情况下,仅触发重绘,什么情况下,会触发回流

仅触发重绘:

  • CSS部分样式改变:例如 背景颜色background-color 透明度opacity,文字颜色color等

触发回流:

  • 页面首次渲染一定会触发回流
  • CSS样式改变:例如height、weight、font-size、padding等样式改变会触发回流
  • 删除or新增DOM节点
  • 内容变化(文本变化、图片变化等)
  • 计算获取属性
如何减少回流?

由于回流的性能开销相对比较大,所以下面我们讨论如何减少回流,这也是我们这篇文章需要介绍的第一个性能优化

将多次CSS修改合并成一次

  • 使用cssText进行合并修改
  • 在js中,使用css的className或者classList,如下:
  el.style.cssText = "width:100px;height:100px;margin:10px";
  el.className = 'my-class'  // 用类名而不是一堆的js代码

使用文档碎片

例如批量增加元素可能会导致频繁回流,使用文档碎片存储增加的操作并且最后直接对文档碎片进行操作,优化减少到一次回流

// 批量添加元素,使用document.createDocumentFragment()优化
const fragment = document.createDocumentFragment();
for(let i = 0 ; i < 10 ; i++)
{
  const el = document.createElement("div");
  fragment.appendChild(el)
}
document.body.appendChild(fragment);

下线操作

将当前需要修改的元素脱离文档流/display:none 减少回流的修改 修改后再嵌入原页面

但其实设置display:absolute等使其脱离文档流 的话是会触发回流的,只不过能够使其脱离文档流减少触发其父元素的回流,仅触发其自身的回流

我们都知道display:none的元素是不会放在渲染树上的,因此修改display:none不会触发回流

来个例子:修改el:

const el = document.getElementById("myEl");
el.style.position = 'absolute';
el.style.display = 'none';s
... 进行大量DOM操作
el.style.display = 'block';
el.style.position = 'static';

缓存布局信息

这一点是针对有些例如offsetHeight,offsetTop等属性 在读取时会强制触发回流更新 确保读到的数据是最新的

但在一些情况下,我们可以用变量记录它来进行缓存,减少每次都需要重复读从而重复回流

使用transform、opcaity属性开启分层

el.style.left = '100px';
// 只触发重绘,性能更好
el.style.transform = 'translateX(100px)';

优化资源加载

我们知道,对于资源的加载其实是很影响性能的好坏的,所以我们可以做一些操作,使得在资源加载上能够更快,更好

图片懒加载

对于未进入视口的图片,可以不需要去下载其图片资源,在我的项目中,使用InterceptionObserver来控制:只有当进入视口的图片,才会将其src设置为真正需要下载的data-src属性

路由懒加载

使用Suspense和lazy这两个API联合完成路由懒加载

资源预加载

使用多路复用,服务器推送协同工作:实现资源预加载

将图片转换为webp

webp是谷歌推出的新的图片格式,能够在保证原清晰度的情况下,将图片体积减少30%左右

使用async和defer加载js资源

我们知道js实际上是会阻塞渲染的,因此,我们可以在script标签上加上这两个类型之一来实现异步下载

  • async 异步下载 不保证顺序 : 应用于不依赖脚本的场景(独立脚本) 可能在DOMContentLoaded前或者后
  • defer 异步下载 不阻塞HTML解析 脚本会在html解析完成后 DomContentLoaded前 应用于需要依赖的场景,比如将vue脚本(同步代码需要依赖vue)的脚本以defer放在头部

优化js

既然提到性能优化了,那js的执行肯定也是我们需要考量的一个点

  • 使用防抖/节流 避免重复执行某个函数,亦或者避免重复向服务器发请求,使用防抖/节流封装返回函数
  • web worker 使用webworker开启多线程
  • requestAnimationFrame 使用requestAnimationFrame优化动画

框架优化

这里就拿react来说

  • 虚拟DOM:通过diff算法,减少频繁操作真实DOM,减少回流
  • memo、useMemo、useCallback:优化组件
  • 利用React Fiber优化 减少一直渲染的时间,可以在一定时间段内执行优先级高的任务

缓存优化

利用强缓存、协商缓存进行性能的优化 事实上有个概念叫code split,可以将不经常更改的文件分割,设置头部标识,将它放在强缓存中

网络优化

  • cdn加速 静态资源,分流,会缓存文件 多路复用 多域名服务器 img1.baidu.com img.baidu.com
  • Gzip压缩
  • HTTP/2 多路复用
  • DNS 预解析

首屏优化

  • SSR
    开启SSR模式组件渲染在服务器端已经完成编译、执行,浏览器端直接显示
  • http 2.0 server push 首屏数据推送

性能测试

当回答“你是如何评价性能的?”这类问题时,面试官实际上是在问你是否有用过一些性能测试的工具?下面作者提供了两个,对于一些不是非常强的大厂,回答到这两点我觉得应该就差不多了,其余的一些性能测试工具,大家也可以自己去整理一下,也欢迎在评论区补充!

chrome 的performance面板

我们可以打开控制台,选择“性能”,就可以看到这个chrome自带的performance面板

image.png

它可以看到各项性能指标,针对性的优化,给出优化建议

具体怎么用的作者在这里就不长篇大论了,大家可以自己去阅读相关文章

lighthouse

lighthouse是chrome 的一款性能打分插件,会在 性能、无障碍、最佳实践、SEO 打分

我们可以在chrome的插件商店去搜索这款插件并下载

image.png

它同样能够给出问题和优化建议,而且非常细致


总结

本文系统地梳理了前端性能优化的知识体系,旨在为开发者,特别是在面试场景中,提供一份清晰的回答思路和实战指南。掌握这些内容,你将能更加自信、系统地回答任何关于性能优化的问题,并有效地应用于实际项目之中