理解防抖和节流的区别
背景:
在前端开发中,我们会经常需要绑定一些持续触发的事件,如resize, scroll, mousemove等,但是有时候不希望在事件持续触发的过程中太频繁地执行函数
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="content" style=" height: 150px; line-height: 150px; text-align: center; color: #fff; background-color: #ccc; font-size: 80px; " ></div> <script> let num = 1; const content = document.getElementById("content"); function count() { content.innerHTML = num++; } content.onmousemove = count; </script> </body> </html>
在上述代码中,div 元素绑定了 mousemove 事件,当鼠标在 div(灰色)区域中移动的时候会持续地去触发该事件导致频繁执行函数
解决方案:防抖和节流
1. 防抖(debounce):是指触发事件后n秒才执行函数,如果在n秒内又触发事件了,则会重新计算函数时间
(case1:做电梯,电梯会等没人进来了,再等一会,才关闭;case2: input输入框,输入内容搜索,在停下输入过一会后,才发起请求获取匹配的数据;case3: 玩游戏读条,只有读条结束才可以输出,如果读条被打断,要重新读条)
1.1 非立即执行版防抖:事件触发后不立即执行,n秒后执行
<script> let num = 1; const content = document.getElementById("content"); function count() { content.innerHTML = num++; } // 非立即执行版:触发事件后函数不会立即执行,而是在n秒后执行, // 如果n秒内又触发了事件,则会重新计算函数执行时间 function debounce(func, wait) { let timeout; return function () { const context = this; const args = [...arguments]; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { func.apply(context, args); }, wait); }; } content.onmousemove = debounce(count, 1000); </script>
1.2 立即执行版防抖:触发事件后函数立即执行,n秒内不触发事件才能继续执行
// 立即执行版: 触发事件后函数会立即执行,然后n秒内不触发事件才能继续执行的效果 function debounce(func, wait) { let timeout; return function () { const context = this; const args = [...arguments]; if (timeout) { clearTimeout(timeout); } const callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait); if (callNow) { func.apply(context, args); } }; }
1.3 结合版本:实际开发过程中,需要根据不同场景来决定使用哪一个版本的防抖函数。上述版本可以合并:
function debounce(func, wait, immediate) { let timeout; return function () { const context = this; const args = [...arguments]; if (timeout) { clearTimeout(timeout); } if (immediate) { const callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait); if (callNow) { func.apply(context, args); } } else { timeout = setTimeout(() => { func.apply(context, args); }, wait); } }; } content.onmousemove = debounce(count, 1000, false);
2. 节流(throttle):是指连续触发事件,但是在n秒中只执行一次的函数。节流会稀释函数的执行频率
(case 1: 看电影,每秒有24帧,意思是每1秒的动画,播放了24张连续的图片;case2: 滚动条加载更多,监听滚动条位置时,设置每1s监听一次,而不是无限次监听;case3: 节流相当于玩游戏某技能的cd,cd时间未到不会执行)
2.1 时间戳版节流:在持续触发事件的过程中,函数会立即执行,并且每1s执行一次
function throttle(func, wait) { var previous = 0; return function () { let now = Date.now(); let context = this; let args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } }; }
2.2 定时器版节流:在持续触发事件的过程中,函数不会立即执行,每1s执行一次,在停止触发事件后,函数会再执行一次
function throttle(func, wait) { let timeout; return function () { let context = this; let args = arguments; if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args); }, wait); } }; }
2.3 时间戳版本节流和定时器节流的区别是,时间戳版本的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。
两者合并,如下版本:
/** * @desc 函数节流 * @param func 函数 * @param wait 延迟执行毫秒数 * @param type 1 表时间戳版,2 表定时器版 */ function throttle(func, wait ,type) { if(type===1){ let previous = 0; }else if(type===2){ let timeout; } return function() { let context = this; let args = arguments; if(type===1){ let now = Date.now(); if (now - previous > wait) { func.apply(context, args); previous = now; } }else if(type===2){ if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } } } }
3. 防抖和节流的区别
- 用户在搜索的时候,在不停敲字,如果每敲一个字我们就要调一次接口,接口调用太频繁,给卡住了。-----用防抖
- 用户在阅读文章的时候,我们需要监听用户滚动到了哪个标题,但是每滚动一下就监听,那样会太过频繁从而占内存,如果再加上其他的业务代码,就卡住了。-----用节流
防抖:结合输入框场景,只有当用户输入完毕一段时间后,才会调用接口,出现联想词
节流:指定时间间隔,只执行一次任务;应用场景:比如,懒加载监听滚动条的位置,使用节流按一定频率获取
使用防抖和节流可以减少不必要的损耗(频繁调取接口,网络堵塞,增加服务器压力)。
最后,汇总
参考:
https://juejin.cn/post/6844903651278848014
https://juejin.cn/post/6844904185117278215