8-1、性能优化

优化思路

1、业务优化;2、流程优化;3、技术优化;

1、先通过业务指标,考量哪些模块值得被优化,就算要优化也要找有价值的业务 模块
2、流程优化:主要指开发流程、设计流程;
开发时:“代码设计”文档优化,多写注释,避免代码集中;
设计时:统一样式风格
3、技术优化:采用前端技术进行优化

技术优化

1、加载优化

少请求;多缓存;预加载

优先使用 http2

可借用 http2 的“流”来提高加载速度

加载时间优化

压缩文件、使用 CDN 存储静态资源,减少资源下载时间

减少 http 请求

减少 http 请求数量:【10 个连接请求 1 k】耗时是大于【1 个连接请求 10 k】

因为发起 http 请求:需要先进行 DNS 寻址,然后 TCP 连接,组装请求报文并发送,等待服务器响应,关闭 TCP 连接
Http1 默认最大并发 6 个 TCP 连接

合并文件、减少不必要的请求。

  • 采用打包工具合并 CSS、JS,需要根据实际文件大小来决定是否合并
  • 使用 CSS Sprite,将背景图片合为一个文件
  • 使用 Base64 图片来代替一些小图片的请求

减少 DNS 查询

浏览器发起请求后,首先要进行 DNS 查询,找到对应的 IP,才能接着干事。
DNS 查询一般耗费 20-120 毫秒。
可以通过减少不同的域名来减少 DNS 查询。
但因为减少了不同的域名,则也会减少资源下载的并发量,所以要合理控制域名数量。
比如:组件分在 2~4 个域名下

使用多个域名

因为浏览器对每个域名有最大并发连接数的限制

Chrome 34/32:6 个
IE 10:8 个
IE 11:13 个
Firefox 27/26:6 个
Safari 7.0.1:6 个

文件放置

CSS 放在 head 中:会更优先加载 CSS,页面就会有一种“逐步渲染的效果”
JS 放在 body 底部:因为 JS 会执行时阻塞渲染,所以放到底部,让渲染更友好,避免长时间白屏。

懒加载、预加载

把不需要的延后加载:适用于图片、模块
把需要的提前加载

避免 301、302

重定向会浪费资源,针对前端来说容易忽略末尾的/,比如:https://xxxx.xx.com/xx 是会重定向到 https://xxxx.xx.com/xx/ 的。
这种服务器也可以处理,但前端要知道~

2、打包优化

代码压缩:使用打包工具提供的压缩工具(如 terser、uglify)减小体积
代码分割:使用打包工具提供的 Chunks 功能,减少首屏加载数量
Tree-Shaking:开启该模式,可剔除无用代码
优化循环和递归:代码里面少用递归,它比较耗性能;可改用单循环,但少用嵌套循环

3、渲染优化

减少重绘和重排

可使用 CSS 合并、动画优化、减少 DOM 操作次数。重排必定触发重绘

具体注意事项:
1、缓存已访问过的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 不建议的写法:函数执行完毕后 EL 就会被释放,再次执行时就又会查询 DOM
function updateElement(textContent) {
cosnt EL = document.getElementById('myElement');
// 修改或读取缓存的元素属性
EL.textContent = textContent
}


// 避免多次查询 DOM:外面声明个变量,存储 DOM
let elementCache;
function updateElement(textContent) {
if (!elementCache) {
elementCache = document.getElementById('myElement');
}
// 修改或读取缓存的元素属性
elementCache.textContent = textContent
}

2、使用 DocumentFragment 暂存 DOM,编辑好后再统一插入

1
2
3
4
5
6
7
8
9
10
11
12
// 创建一个文档片段
const fragment = document.createDocumentFragment();

// 在文档片段中构建新的DOM结构
for (let i = 0; i < 100; i++) {
const newElement = document.createElement('div');
newElement.textContent = `Item ${i}`;
fragment.appendChild(newElement);
}

// 将整个文档片段一次性添加到DOM树中,只触发一次重绘和重排
document.getElementById('container').appendChild(fragment);

3、使用 className 来操作元素的样式

1
2
3
4
5
6
7
8
9
10
// 定义类名样式在CSS文件中
.active {
color: red;
}

// JavaScript 中切换类名而非直接修改style属性
function toggleActive() {
const element = document.getElementById('targetElement');
element.classList.toggle('active');
}

4、避免使用 JS 操作布局: 减少动态设置与布局相关的样式(如 offsetWidth, offsetHeight, clientWidth 等),因为获取这些值时可能触发重排。

1
2
3
4
5
6
7
8
// 避免这样做,因为它会触发重排
function setWidthBasedOnOtherElement() {
const source = document.getElementById('source');
const target = document.getElementById('target');
target.style.width = source.offsetWidth + 'px'; // 这将导致浏览器重排
}

// 更好的做法是提前在 CSS 中通过 flex 布局或其他方式关联两个元素的宽度

5、框架内合理使用 v-if、v-show

1
2
v-if:会触发重排
v-show:只会触发重绘

6、使用 RAF:requestAnimationFrame(按帧执行) 来优化动画效果
若我们采用定时器来实现复杂动画时,它不会考虑到浏览器的渲染时机,所以各干各的,然后频繁重排与重绘
但使用了 RAF 后,它会跟浏览器渲染打好配合,在每次重绘前调一下,跟着渲染节奏执行,这样性能与流畅更好

1
2
3
4
5
6
7
8
function moveAnimate() {
// 动画效果
// 获取元素,计算大小,改变位置等等

requestAnimationFrame(moveAnimate) // 请求下一帧动画
}

requestAnimationFrame(moveAnimate) // 启动动画

前端路由懒加载

使用异步加载写法,等需要用时才会触发对应页面的加载

使用 WebWorkers 处理密集或耗时的任务

简单理解为 JS 的限制性分身们,互相独立并可通信,但不可访问 DOM。
可以开多个 WebWorkers,实现 JS 的多线程

场景:数据处理、图像处理、大量计算、网络请求等
使用 postmessage() 进行通信

ServiceWorkers:一种特殊的 WebWorkers,专注于网络代理与离线缓存


8-1、性能优化
https://mrhzq.github.io/职业上一二事/前端面试/前端八股文/8-1、性能优化/
作者
黄智强
发布于
2024年1月13日
许可协议