1. 前言
首先来举个例子。百度首页的百度输入框,用户输入的时候,每次输入的信息,我们都能看到百度服务器返回给我们的联想关键字。我们每改动一个字,它就换一次联想词,这是我们肉眼能看到的速度,实际上如果不加以处理,可能已经上服务器发起了好几十次的同一个关键字联想请求了,具体速度依赖于不同的pc等机器上的运行速度不同。那么,刚刚也谈到,对于同一个关键字,请求这么多次,也许想给用户呈现的就一次,剩下的请求都是浪费的,并且如果成千上万甚至上亿的用户同时请求,对服务器的负担是巨大的。
防抖节流解决的问题:
在连续触发的事件中,事件处理函数的频繁调用会加重浏览器或服务器的性能负担导致用户体验糟糕,有哪些连续触发的事件呢 ?
比如,浏览器滚动条的滚动事件、浏览器窗口调节的resize事件、输入框内容校验以及在移动端的touchmove事件等。
所以,我们将采用防抖函数(debounce )和节流函数(throttle)来限制事件处理函数的调用频率。
总的来说:防抖函数(debounce )和节流函数(throttle)是在时间轴上控制函数的执行次数。
2. 函数防抖(debounce)
延迟防抖
延迟防抖(debounce): 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
生活中的实例: 如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次。
如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
如下图,持续触发click事件时,并不执行handle函数,当1000毫秒内没有触发click事件时,才会延时触发click事件。
前缘防抖
执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。
为什么要这样呢?
试想第一种延迟debounce,我们本来想对用户输入的关键字,发起请求联想的频率降低,但是如果用户在我们设定的时间中,一直输入,导致的就是,用户一直看不到关键字,我们倒不如第一次输入的时候就发起一个请求,服务器返回结果,呈现给用户,然后后续用户的键入结束在继续请求)。
<!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>防抖</title> </head> <body> <button id="debounce1">点我防抖呐!</button> <script> function handle() { console.log("防抖成功!"); } window.onload = function() { // 1. 获取按钮,并绑定事件 var myDebounce = document.getElementById('debounce1'); myDebounce.addEventListener('click',debounce(handle,1000,true)); } // 防抖函数 function debounce(fn,wait, immediate ){ //2. 设置时间戳,使用setTimeout让返回函数延迟执行 let timer, result; return function(...args){ // 3. timer存在,将定时器中的函数清除 if(timer) clearTimeout(timer); // 4.1 立即执行返回函数 if(immediate){ if(!timer){ result = fn.apply(this,args); } timer = setTimeout(() => { timer = null; },wait); }else{ // 4.2 非立即执行返回函数 timer = setTimeout(() => { fn.apply(this,args); },wait); } } // 5. 立即执行时返回函数的返回值 return result; } </script> </body> </html>
实现效果:
原理解析:
- 防抖函数作用,对传入的函数进行延时包装后返回
- setTimeout在前一次未执行完前,第二次次触发将会覆盖掉前面的定时器,执行第二次的功能
- 前一次由于异步加延时还未执行完,使用clearTimeout清除前面定时器,取消上次的fn功能
- 为保持fn内部this的指向,使用apply改变this指向
- fn传入为函数,不是函数的调用
防抖函数实现总结
function debounce(fn,wait, immediate ){ //2. 设置时间戳,使用setTimeout让返回函数延迟执行 let timer, result; return function(...args){ // 3. timer存在,将定时器中的函数清除 if(timer) clearTimeout(timer); // 4.1 立即执行返回函数 if(immediate){ if(!timer){ result = fn.apply(this,args); } timer = setTimeout(() => { timer = null; },wait); }else{ // 4.2 非立即执行返回函数 timer = setTimeout(() => { fn.apply(this,args); },wait); } } // 5. 立即执行时返回函数的返回值 return result; }
3. 函数节流(throttling)
throttling,节流的策略是,固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。 节流策略也分前缘和延迟两种。
与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。
- 节流会稀释函数的执行频率
- 在持续触发事件的过程中,函数会立即执行,并且每n秒执行一次
生活中的实例: 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源
延迟节流
前缘节流
<!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>节流</title> </head> <body> <button id="debounce1">点我节流呐!</button> <script> function handle() { console.log("节流成功!"); } window.onload = function() { var myDebounce = document.getElementById('debounce1'); myDebounce.addEventListener('click',throttling(handle,1000,false)); } // 节流函数 function throttling(fn,wait,immediate){ let timer; return function(...args) { if(!timer){ if(immediate){ fn.apply(this,args); } timer = setTimeout(() => { if(!immediate) { fn.apply(this,args); } timer = null; },wait); } } } </script> </body> </html>
实现效果:
节流函数实现总结
<!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>节流</title> </head> <body> <button id="debounce1">点我节流呐!</button> <script> function handle() { console.log("节流成功!"); } window.onload = function() { var myDebounce = document.getElementById('debounce1'); myDebounce.addEventListener('click',throttling(handle,1000,false)); } // 节流函数 function throttling(fn,wait,immediate){ let timer; return function(...args) { if(!timer){ if(immediate){ fn.apply(this,args); } timer = setTimeout(() => { if(!immediate) { fn.apply(this,args); } timer = null; },wait); } } } </script> </body> </html>
4. 两者区别
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。
函数防抖只是在最后一次事件后才触发一次函数。
比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
5. 应用场景
对于函数防抖,有以下几种应用场景:
- 给按钮加函数防抖防止表单多次提交。
- 对于输入框连续输入进行AJAX验证时,用函数防抖能有效减少请求次数。
- 判断scroll是否滑到底部,滚动事件+函数防抖
总的来说,适合多次事件一次响应的情况
对于函数节流,有如下几个场景:
- 游戏中的刷新率
- DOM元素拖拽
- Canvas画笔功能
总的来说,适合大量事件按时间做平均分配触发。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注阿兔在线工具的更多内容!