节流或防抖:控制函数的执行
在浏览器里面有很多交互行为:点击按钮出现弹窗,滑动鼠标滚轮窗口内容随之移动,缩放浏览器窗口引起页面的重新布局,发送HTTP请求等待服务器相应等等。在这些例子中,前者(点击按钮,滑动鼠标滚轮,缩放浏览器窗口,发送HTTP请求)是事件,后者(出现弹窗,窗口内容改变)是响应,这个过程即是事件驱动。
事件驱动有一个明显缺点:事件发生很频繁就会造成性能问题。
比如:某个函数根据窗口大小来操作DOM,倘若用户在很短的时间内,频繁缩放和扩大浏览器窗口,函数就会不必要地频繁执行。因为JS是单线程的,这样,其它的事件响应可能就没有办法执行,造成页面卡顿或是无响应等。
事件发生的频率和次数没有办法控制,因此只能控制事件响应的频率或是次数,来保证正常的用户体验。
控制函数的执行频率和次数有两种方法:1、Throttle,函数节流 2、Debounce,函数防抖。
Throttle函数节流
Throttle是节流阀的意思。1s内执行10次函数,则频率为10。如果可以控制函数执行的频率,1s内执行2次函数,则频率为2,就好像水龙头拧得小了一点。
在页面中监听mousemove事件,移动鼠标,回调函数执行的频率会很高,可以调整其频率来保证性能,也就是在最小时间段内只能执行一次函数。
let old = 0; const delta = 1000; function throttleFun(fn) { const now = Date.now(); if (now - old >= delta) { // 在1000ms内log函数只能执行一次,保证频率不能过高 log(); old = now; } else { // 在快速移动鼠标过程中,可以看到控制台的输出下面的内容 // 而真正的目标函数最多也只能在1s内执行1次 console.log("move too fast") } } function log(){ console.log('I\'m log') } window.onmousemove = throttleFun;
Debounce函数防抖
对于电子器械,按下一个按钮,由于物理材料,接触情况,传导性能等因素,可能会产生多个电流信号,就好像“手抖按了多次按钮”一样。因此,“防抖机制”就很有必要,可以保证在短时间的多次操作只有最后一次操作才有效果。
防抖原理:事件第一次发生,先不响应,事件第二次发生,计算两次事件发生的时间间隔,若时间间隔小于某个值,则认为是“手抖操作”,函数仍旧不响应,事件第三次发生,计算第二次、第三次发生的时间间隔。
let old = 0; const delta = 1000; function debounceFun(fn) { const now = Date.now(); if (now - old >= delta) { // 只有两次事件发生的间隔大于1000ms,log函数才会执行。保证没有误操作 log(); } else { console.log("move too fast") } // 事件发生,都需要标记事件发生的时间 // 以便下次事件发生时,计算时间间隔 old = now; } function log(){ console.log('I\'m log') } window.onmousemove = debounceFun;
使用setTimeout的写法,重点在于,判断在某个时间段内,如果有一个函数在队列中,则更新队列中的函数。
let timerId; const delta = 1000; function debounceFun(fn) { if(timerId > 0) { // 如果大于0,则说明delta时间段内,上一个函数还没有执行 // 直接清除,更新队列中的函数 clearTimeout(timerId) } timerId = setTimeout(() => { log() }, delta); } function log(){ console.log('I\'m log') } window.onmousemove = debounceFun;
[参考资料]:
1、 Timing Controls https://zhirzh.github.io/2016/10/11/timing-controls/
2、 JS魔法堂:函数节流(throttle)与函数去抖(debounce) http://www.cnblogs.com/fsjohnhuang/p/4147810.html