性能优化的原则和方向

原则

  • 多使用内存、缓存或其他方法
  • 减少 CPU 计算量,减少网络加载耗时
  • 适用于所有编程的性能优化-空间换时间

从何入手

  1. 让加载更快
  • 减少资源体积:压缩代码
  • 减少访问次数:合并代码、SSR 服务端渲染、缓存
  • 使用更快的网络:CDN
  1. 让渲染更快
  • css 放在 head, js 放在 body 最下面
  • 尽早开始执行 js,用 DOMContentLoaded 出发
  • 懒加载(图片懒加载,上滑加载更多)
  • 对 DOM 查询进行缓存
  • 频繁 DOM 操作,合并到一起插入 DOM 结构
  • 节流 throttle 防抖 debounce,让渲染更加流畅

示例

资源合并

缓存

  • 静态资源加 hash 后缀,根据文件内容计算 hash
  • 文件内容不变,则 hash 不变,则 url 不变
  • url 和文件不变,则会自动出发 http 缓存机制,返回 304

配置 webpack 打包时,根据文件内容生成 hash 后缀

  • CDN 静态文件会加载很快

SSR

服务器渲染:将网页和数据一起加载,一起渲染
非SSR(前后端分离):先加载网页,再加载数据,再渲染数据
早先的 JSP ASP PHP ,现在的 vue react SSR

懒加载

1
2
3
4
5
6
<img id="img1" src="preview.png" data-realsrc="abc.png">
<script>
// 图片露出屏幕时,图片距离屏幕底部的值
var img1 = document.getElementById('img1')
img1.src = img1.getAttribute('data-realsrc')
</script>

参考

缓存 DOM 查询

1
2
3
4
5
6
7
8
9
10
11
// 不缓存 DOM 查询结果
for(let i = 0;i<document.getElementById('div1').length;i++){
// 每次循环都要计算length,频繁进行 DOM 查询
}

// 缓存 DOM 查询结果
let list = document.getElementById('div1')
const l = list.length
for(let i = 0;i<l;i++){
// 缓存length,只进行一次 DOM 查询
}

多个DOM操作一起插入到DOM结构

1
2
3
4
5
6
7
8
9
10
11
12
13
let list = document.getElementById('div1')

// 创建一个文档片段,此时还没有插入到 DOM 树中
const frag = document.createDocumentFragment()

// 执行插入
for(let i = 0;i<10;i++){
const li = document.createElement("li")
li.innerHTML = "list item" + i
frag.appendChild(li)
}
// 都完成之后再插入 DOM 树中
list.appendChild(frag)

尽早开始js执行

1
2
3
4
5
6
window.addEventListener('load', function(){
// 页面的全部资源加载完成才会执行,包括图片、视频等
})
document.addEventListener('DOMContentLoaded', function(){
// DOM渲染完即可执行,此时图片、视频可能没有加载完
})

防抖

应用场景:监听一个输入框的,文字变化后出发change时间,直接用keyup事件,则会频繁触发change事件,防抖:用户输入结束活暂停时,才会触发change,减少请求次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input type="text" id="input1">
<script>
const input1 = document.getElementById('input1')

let timer

input1.addEventListener('keyup',function(){
if(timer){
clearTimeout(timer)
}
timer = setTimeout(()=>{
console.log(input1.value) // 模拟触发change事件
timer = null // 清空定时器
},500)
})
</script>

封装防抖函数,返回是一个函数没所以形成了一个闭包,有时候要传this和参数,所以不要使用箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 防抖
function debounce(fn, delay = 500){
// timer是在闭包中的
let timer = null

return function(){
if(timer){
clearTimeout(timer)
}
timer = setTimeout(()=>{
fn.apply(this, arguments) // 模拟触发change事件
timer = null // 清空定时器
},delay)
}
}
input1.addEventListener('keyup', debounce(function(){
console.log(input1.value)
}), 600)

节流 throttle

应用场景:拖拽一个元素时,要随时拿到该元素被拖拽的位置,直接用drag事件,则会频繁出发,很容易导致卡顿,节流:无论拖拽速度多快,都会每隔100ms触发一次

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="div1" draggable="true"></div>
<script>
const div1 = document.getElementById('div1')
let timer = null
div1.addEventListener('drag', function(e){
if(timer){
return
}
timer = setTimeout(()=>{
console.log(e.offsetX, e.offsetY)
},100)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 节流
function throttle(fn, delay = 100){
let timer = null
return function(){
if(timer){
return
}
timer = setTimeout(()=>{
fn.apply(this, arguments)
},delay)
}
}
div1.addEventListener('drag',throttle(function(e){
console.log(e.offsetX, e.offsetY)
}),200)

防抖和节流区别

参考

  1. 防抖(debounce):触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间

举例:就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。

  1. 节流(thorttle):高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率

举例:预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。

  1. 区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。