性能优化

这是一个比较大的话题.包含了很多部分.总体来讲就是尽量快的加载页面,使用资源又尽量少.

从输入网址到页面渲染,发生了啥

这道面试题就比较宽范的触及到性能优化的各个点.需要比较系统的去理解.

阻塞渲染

想要首屏优化,就得先把页面渲染出来,什么会阻塞渲染呢?
HTML 和 CSS 会,因为他们要合成渲染树.所以就要降低要渲染的文件大小,扁平层级,优化选择器.
还有一个问题,script会暂停 DOM 渲染.所以一般script会放在 body 后面.
不想放底部就可以使用deferasync属性.两个都是异步下载,和 dom 并行.区别就是执行了.
下载是没区别的.
defer:要等到 DOM 执行完,再按照顺序执行.
async: 下载完就执行,不按顺序,谁先完事谁先跑.

为什么操作 DOM 慢

dom 属于渲染引擎,js 属于 js 引擎,通过 js 操作 dom 就涉及到两个线程之间的通信,就会带来性能损耗.还有可能带来回流重绘的情况,导致性能问题.

插入几万个 DOM,如何实现页面不卡顿

肯定不能一次性加载,需要分批次加载.
方法一: 通过requestAnimationFrame的方式循环插入 DOM.
方法二: 虚拟滚动.只渲染可视区域的内容.当用户滚动,实时替换渲染的内容.避免空白记得加上预渲染.

HTML 优化

就是在不考虑,网络和缓存的情况下,如何加快渲染?

  • 从文件大小考虑
  • 从 script 标签使用上来考虑
  • 从 CSS、HTML 的代码书写上来考虑
  • 从需要下载的内容是否需要在首屏使用上来考虑

图片懒加载

html 实现

给图片加上loading=‘lazy’

1
<img src'./a.png' loading='lazy' />

JS 实现懒加载

原理:拿到所有图片的 DOM,遍历每个图片是否到了可视区域,到了就设置 src 属性,绑定scroll事件进行事件监听。

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
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">
<body>
<img src="./img/default.png" data-src="./img/1.png" />
<img src="./img/default.png" data-src="./img/2.png" />
<img src="./img/default.png" data-src="./img/3.png" />
</body>
<script>
function lazyLoad() {
// 可视区高度
let viewHeight =
window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight;
// 所有图片
let imgs = document.querySelectorAll("img[data-src]");
// 循环
imgs.forEach((item, index) => {
if (item.dataset.src === "") return;
// 获取该元素在视口位置
let rect = item.getBoundingClientRect();
// 可视区域的高度 > 元素到顶部的高度 => 元素露头了
if (rect.top < viewHeight) {
item.src = item.dataset.src;
item.removeAttribute("data-src");
}
});
}
// 这里还需要使用节流函数
window.addEventListener("scroll", lazyLoad);
</script>
</html>

intersectionObserver

Intersection Observer 即重叠观察者,从这个命名就可以看出它用于判断两个元素是否重叠,因为不用进行事件的监听,性能方面相比 getBoundingClientRect 会好很多
目标元素与视口产生交叉区域。
IntersectionObserver 是浏览器原生提供的构造函数,接受两个参数:callback 是可见性变化时的回调函数,option 是配置对象(该参数可选)。
目标元素的可见性变化时,就会调用观察器的回调函数 callback。callback 一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const imgs = document.querySelectorAll("img[data-src]");
const config = {
rootMargin: "0px",
threshold: 0,
};

const callback = (entries, self) => {
entries.forEach((entry) => {
let img = entry.target;
let src = img.dataset.src;
if (src) {
img.src = src;
img.removeAttribute("data-src");
}
// 解除观察
self.unobserve(entry.target);
});
};
// 传入回调
let observer = new IntersectionObserver(callback, config);
// 开始观察
imgs.forEach((img) => observer.observe(img));

JS 性能优化

CSS 性能优化

问题: css 层级设置过多导致性能减弱

1
2
3
4
5
6
7
8
9
10
11
<div>
<a> <span></span> </a>
</div>
<style>
span {
color: red;
}
div > a > span {
color: red;
}
</style>

明显第一种要比第二种效率高得多.减少了因查找递归的过程带来的性能损失.
防范: 尽量避免写过于具体的 CSS 选择器,对 HTML 尽量减少无意义的标签.

回流(reflow)和重绘(repaint)

  • 回流: 回的是文档流,都要快加载好了,又倒回去了.就是回流.布局或者几何属性发生的变化.
  • 重绘: 重新绘画,就是上色.就是外观发生的变化.
  • 回流肯定重绘,重绘不一定回流.

1、添加、删除元素(回流+重绘)
2、隐藏元素,display:none(回流+重绘),visibility:hidden(只重绘,不回流)
3、移动元素,如改变 top、left 或移动元素到另外 1 个父元素中(重绘+回流)
4、改变浏览器大小(回流+重绘)
5、改变浏览器的字体大小(回流+重绘)
6、改变元素的 padding、border、margin(回流+重绘)
7、改变浏览器的字体颜色(只重绘,不回流)
8、改变元素的背景颜色(只重绘,不回流)

减少

  • CSS 选择符从右往左匹配查找,避免节点层级过多
  • 不要使用 table 布局
  • 不要把节点的属性值放在一个循环里当成循环里的变量
  • 使用 visibility 替换 display: none

网络性能优化

框架性能优化