2020/Aug/4 我把节流和防抖概念认错了 现在已更正。
开篇废话
这篇文章还是对 ugoira.huggy.moe 做优化的时候顺便写的
上一篇文章为 《一个在可视界面内加载前端的实例》
在上一篇文章提到了这句
// 如果是监听滚动事件,那么优化要做好来,我直接监听的话滚动就开始卡了(因为滚动一下就会 update 一下)
正好今天看到了关于节流与防抖的概念,正好学习了一波然后写了这篇文章 ~~结果发现
分析
节流和防抖 思路都是类似的,都是在一段时间内少真正执行点事件。
区别在于,节流是在一个事件内隔x毫秒执行一次,而防抖是只执行一次
防抖 (debounce)
防抖的意思大概就是节省无意义的流量(只在开始或者结束执行),比如在一个搜索里面的联想,正在输入中的数据就不需要 post 到服务端进行搜索了,浪费流量与资源 当然 想分析用户曾经输入过什么当我没说。
比如下面的视频例子用户点击登录按钮后,在转圈圈的期间登录按钮不可点了,等待服务器返回结果后恢复正常
另外一个例子:在 Vue 的文档里面的搜索,很明显在我输入完 model 完以后过了一会,才弹出搜索结果,这样就改善了用户体验 + 服务端压力
节流(throttle)
这个比较用于浏览器的 scroll
mousemove
resize
之类的事件中。
用户实际上 拖动(resize)/滚动(scroll)/移动鼠标(mousemove)是一个过程,而不是立刻就更改成相关数值,所以浏览器会一直把相关事件传给监听函数中,如果函数逻辑写得复杂点又或者带请求(也就是会耗点时)来不及跟上 DOM 更新 或者打爆后端,所以需要有节流的思想,隔一段事件再真正执行相关事件,减少点渲染(后端)压力
实践
我们先把 document.addEventListener('scroll', scrollevent)
加回来,看看有什么数据可以让我们判断防抖/节流的
另外提示
window.scrollY
在触屏设备上是返回浮点数的,先把数值砍成 int 类型好点
在这个图里:
- 左边为 触摸(windows)引起的 scroll 事件 右边是鼠标(macOS)滚动引起的 scroll 事件
- 浏览器已经会尝试少点执行 scroll 事件了,没有动一下就执行一次事件, scroll 事件间隔大概在 10-20ms 之间
动手实践:
html:(足够长能滚动就行)
body {
padding-top: 2333px;
}
js:
let lastScrollTime = 0
document.addEventListener('scroll',()=>{
console.log(window.scrollY, +new Date() - lastScrollTime)
lastScrollTime = +new Date()
})
在了解浏览器会在什么时候传给我们什么数据,以及我们能拿什么数据来进行防抖判断后,我们才能正式开始实践。
实践防抖 (debounce)
大概思路就是,判断函数执行的时间差,如果时间差大于x毫秒,那么就真正执行相关函数(这里的 hit throttling就是),然后将上次执行的时间赋值一下,等待下一个x毫秒再执行
判断时间法
let lastScrollTime = 0
document.addEventListener('scroll', () => {
let currentScrollTime = +new Date()
let offset = currentScrollTime - lastScrollTime
if (currentScrollTime - lastScrollTime > 300) {
console.log('hit throttling', window.scrollY, offset)
lastScrollTime = currentScrollTime
} else {
console.log('no throttling', window.scrollY, offset)
}
})
取消监听法
大概思路就是,在一段时间内只执行第一次的事件,然后把关联到的 event 清空,等待x秒后再 listen 回去
动手实践:
const scrollEventTimerF = ()=>{
console.log('trigger',new Date())
document.removeEventListener('scroll',scrollEventTimerF)
setTimeout(() => {
document.addEventListener('scroll',scrollEventTimerF)
}, 2000)
}
document.addEventListener('scroll',scrollEventTimerF)
实践节流 (throttle)
延迟法
我们可以使用 setTimeout
在用户结束滚动后 x毫秒后执行真正要操作的函数
以滚动函数触发的时间的间隔来判断是否需要滚动(触发延迟为固定的 x毫秒)
(感觉一般业务设置 100ms / 500ms 足够了,前面也说了浏览器喂给我们的 scroll 事件一般在 20ms)
实践一下:
// 记录上次滚动事件触发的时间
let lastScrollTime = 0
const scrollEventTimerF = ()=>{
let currentScrollTime = +new Date()
lastScrollTime = currentScrollTime
// 延迟 300 ms
setTimeout(() => {
// 相同代表是最后滚动的事件,那么判断就生效,执行业务代码
if(lastScrollTime == currentScrollTime){
let Y = window.scrollY.toString().split('.')[0]
// let Y = Math.floor(window.scrollY)
console.log(Y,new Date())
}
}, 300)
}
document.addEventListener('scroll',scrollEventTimerF)
这样虽然还是要被 20ms 内触发一次滚动函数 +new Date() 还是要被疯狂执行
不过已经可以避免后面可能的操作 Dom 且无效的操作(没滚动完就渲染页面跟没渲染一样),这样也算优化了
// 其它办法好像没想到了,因为好像怎么样都要延迟一下,只能说是把 lastScrollTime 给换成 lastY(当前页面位置)
此部分代码实例可以在 github/ugoira.huggy.moe 找到
如图所示,三次事件触发间隙在 2 秒以上
定义状态法
我们定义个状态标记下事件是否执行完毕
这是我的 ugoira.huggy.moe 的例子
如果 webStatus
(当前状态) 包含 d
, c
, p
那么就不执行这个转换函数了。
总结
在写节流和防抖逻辑的时候,就是首先要想想有什么数据/事件我能够判断的,然后哪些可以节省的,利用这些给现有代码多加几个判断还有 setTimeout
就完事了。
另外对于防抖与节流其实有更好的代码封装,与原理演示,可以参考这篇文章:
当然,业务逻辑要是只需要根据延迟的话,直接拿现成的库也不错:
最后:代码写的不好,有不严谨的多多包含
完