浏览器性能优化

当页面元素较多时浏览器的渲染自然也成了一个大问题,而如果处理得当我们可以帮助用户省下很多流量,减少白屏时间、内存占用,优化用户体验。

重绘(Repaint)和回流(Reflow

重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。

重绘:节点需要更改外观而不会影响布局(比如对color的改变)

回流:布局或集合属性需要改变

回流必定发生重绘,而重构会则不一定引发回流。回流所需成本必重绘高得多,改变深层次的节点很可能导致父节点的一系列回流

以下几个动作可能会导致性能问题

  • 改变window大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或浮动
  • 盒模型

很多人不知道的是,重绘和回流其实和 Event loop 有关。

  1. 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
  2. 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resizescroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
  3. 判断是否触发了 media query
  4. 更新动画并且发送事件
  5. 判断是否有全屏操作事件
  6. 执行 requestAnimationFrame 回调
  7. 执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
  8. 更新界面
  9. 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。

以上内容来自于 HTML 文档

减少重绘和回流

  1. 使用translate替代top
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="test"></div>
<style>
.test {
position: absolute;
top: 10px;
width: 100px;
height: 100px;
background: red;
}
</style>
<script>
setTimeout(() => {
// 引起回流
document.querySelector('.test').style.top = '100px'
}, 1000)
</script>
  1. 使用visibility替换display: none。前者只会引起重绘,后者会引发回流(改变了布局)
  2. 把 DOM 离线后修改:(比如,先把 DOM 给display: none,再修改100次,再显示出来。用于频繁修改的 DOM 结构)
  3. 不要把 DOM 节点的属性值放在一个循环里当成循环里的变量
1
2
3
4
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
  1. 不要使用table布局。可能很小的一个改动就会造成整个table的重新布局
  2. 动画实现的速度选择:动画速度越快,回流次数越多。当然也可以选择使用requestAnimationFrame
  3. CSS 选择符从右往左匹配查找,避免 DOM 深度过深
  4. 将频繁运行的动画变为图层。图层能够阻止该节点回流影响别的元素。比如对于video标签,浏览器会自动将该节点变为图层

图片优化

计算图片大小

对于一张 100 100 像素的图片来说,图像上有 10000 个像素点,如果每个像素的值是 RGBA 存储的话,那么也就是说每个像素有 4 个通道,每个通道 1 个字节(8 位 = 1个字节),所以该图片大小大概为 39KB(10000 1 * 4 / 1024)。

但是在实际项目中,一张图片可能并不需要使用那么多颜色去显示,我们可以通过减少每个像素的调色板来相应缩小图片的大小。

了解了如何计算图片大小的知识,那么对于如何优化图片,想必大家已经有 2 个思路了:

  • 减少像素点
  • 减少每个像素点能够显示的颜色

图片加载优化

不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。

对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。

小图使用 base64 格式

将多个图标文件整合到一张图片中(雪碧图)

选择正确的图片格式:

  • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
  • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
  • 照片使用 JPEG

其他文件优化

CSS 文件放在 head

服务端开启文件压缩功能

script 标签放在 body 底部,因为 JS 文件执行会阻塞渲染。当然也可以把 script 标签放在任意位置然后加上 defer ,表示该文件会并行下载,但是会放到 HTML 解析完成后顺序执行。对于没有任何依赖的 JS 文件可以加上 async ,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行。

执行 JS 代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑使用 WebworkerWebworker 可以让我们另开一个线程执行脚本而不影响渲染。

CDN

全称:Content Delivery Network或Content Ddistribute Network,即内容分发网络。

基本思路:尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。

静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie。

Webpack

优化打包速度
  • 减少文件搜索范围
    • 比如通过别名
    • loader 的 test,include & exclude
  • Webpack4 默认压缩并行
  • Happypack 并发调用
  • babel 也可以缓存编译
Babel 原理

本质就是编译器,当代码转为字符串生成 AST,对 AST 进行转变最后再生成新的代码

  • 分为三步:词法分析生成 Token,语法分析生成 AST,遍历 AST,根据插件变换相应的节点,最后把 AST 转换为代码
如何实现一个插件
  • 调用插件 apply 函数传入 compiler 对象
  • 通过 compiler 对象监听事件

比如你想实现一个编译结束退出命令的插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BuildEndPlugin {
apply (compiler) {
const afterEmit = (compilation, cb) => {
cb()
setTimeout(function () {
process.exit(0)
}, 1000)
}

compiler.plugin('after-emit', afterEmit)
}
}

module.exports = BuildEndPlugin