对防抖和节流的一些理解,做一次记录。(之前项目中的需求是在输入框中输入内容之后,调接口返回值,然后不知道还有节流这波操作,然后就写了判断当鼠标失去焦点的时候调接口,后来大佬说可以使用节流来实现)
防抖和节流算起来应该属于性能优化的知识,但是处理不当或者是放任不管就容易引起浏览器卡死。就是在绑定scroll、resize这类事件时,当他发生时,被触发的频率非常高,间隔很近。如果事件中涉及到大量的位置计算、DOM操作、元素重绘等工作且这些工作无法在下一个scroll事件触发前完成,就会造成浏览器调帧。加之用户鼠标滚动往往时连续的,就会持续触发scroll事件导致调帧扩大、浏览器CPU使用率增加、用户体验受到影响。尤其时在涉及与后端的交互中,前端依赖于某中事件如resize、scroll,发送http请求,在这个过程中,如果不做防抖处理,那么在事件触发的一瞬间,就会有很多个请求发过去,增加了服务端的压力。
1.从滚动条监听的例子说起
先说一个常见的功能,很多网站会提供一个按钮:用于返回顶部。
这个按钮只会在滚动到距离顶部一定位置的时候才会出现,那么现在抽象出这个功能需求 --- 监听滚动条事件,返回当前滚条和顶部的距离。
这个需求很简单,直接写:
1 function showTop () { 2 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 3 console.log(‘滚动条位置:‘ + scrollTop); 4 } 5 window.onscroll = showTop
但是:在运行的时候会发现:这个函数的默认执行频率太高了!以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!
然而实际上并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以需要优化这种场景。
2.防抖
基于上述的场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值:300ms
- 如果在300ms内没有再次触发滚动事件,那么就执行函数。
- 如果在300ms内再次触发滚动事件,那么当前的即使取消,重新开始计时。
效果就是:如果在短时间内大量触发同意事件,只会执行一次函数。
实现:既然前面都提到了计时,那实现的关键就在于setTimeOut
这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:
1 /** 2 * fn[function] 需要防抖的函数 3 * delay[number] 毫秒,防抖期限值 4 */ 5 function debounce(fn,delay){ 6 let timer = null; 7 return function(){ 8 if(timer){ 9 //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时 10 clearTimeout(timer) 11 timer = setTimeOut(fn,delay) 12 }else{ 13 // 进入该分支说明当前并没有在计时,那么就开始一个计时 14 timer = setTimeOut(fn,delay) 15 } 16 } 17 }
当然 上述代码是为了贴合思路,方便理解。写完会发现其实timer = setTimeOut(fn,delay)
是一定会执行的,所以可以稍微简化下:
1 function debounce(fn,delay){ 2 let timer = null //借助闭包 3 return function() { 4 if(timer){ 5 clearTimeout(timer) 6 } 7 timer = setTimeout(fn,delay) // 简化写法 8 } 9 } 10 // 然后是旧代码 11 function showTop () { 12 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 13 console.log(‘滚动条位置:‘ + scrollTop); 14 } 15 window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置
此时会发现,必须在停止滚动1s以后,才会打印出滚动条位置。
防抖也就实现了:定义即:
- 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。
3.节流
继续思考,使用上面的防抖方案来处理问题的结果是:
- 如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离。
但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?
其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。
效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
实现 这里借助setTimeout
来做一个简单的实现,加上一个状态位valid
来表示当前函数是否处于工作状态:
定时器方案
1 function throttle(fn,delay){ 2 let valid = true; 3 return function(){ 4 if(!valid){ 5 return false; 6 } 7 //执行函数并且在间隔期间内把状态位设为无效 8 valid = false; 9 setTimeout(()=>{ 10 fn() 11 valid = true; 12 },delay) 13 } 14 } 15 /* 请注意,节流函数并不止上面这种实现方案, 16 例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。 17 也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样 18 */ 19 // 以下照旧 20 function showTop () { 21 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 22 console.log(‘滚动条位置:‘ + scrollTop); 23 } 24 window.onscroll = throttle(showTop,1000)
运行以上代码的结果:
如果一直拖着滚动条进行滚动,那么会以1s的时间间隔,持续输出当前位置和顶部的距离。
时间戳方案
1 var throttle = function(fn,delay){ 2 var prev = Date.now(); 3 return function(){ 4 var context = this; 5 var args = arguments; 6 var now = Date.now(); 7 if(now -prev >=delay){ 8 fn.apply(context,args) 9 prev = Date.now(); 10 } 11 } 12 } 13 function handle(){ 14 console.log(Math.random()); 15 } 16 window.addEventListener(‘scroll‘,throttle(handle,1000));
时间戳+定时器
1 var throttle = function(func, delay) { 2 var timer = null; 3 var startTime = Date.now(); 4 return function() { 5 var curTime = Date.now(); 6 var remaining = delay - (curTime - startTime); 7 var context = this; 8 var args = arguments; 9 clearTimeout(timer); 10 if (remaining <= 0) { 11 func.apply(context, args); 12 startTime = Date.now(); 13 } else { 14 timer = setTimeout(func, remaining); 15 } 16 } 17 } 18 function handle() { 19 console.log(Math.random()); 20 } 21 window.addEventListener(‘scroll‘, throttle(handle, 1000));
4.其他应用场景举例
讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:
- 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
- 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)
5.总结
函数防抖:将几次操作合并为一个操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay内再次触发的话,就会取消之前的计时器而重新设置。这样一来。只有最后一次操作能被触发。
函数节流:使得一定时间内只触发一次函数,原理是通过判断是否到达一定时间来触发函数。
区别:函数节流不管事件触发多频繁,都会保证在规定的时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。比如在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次ajax请求,而不是在啊用户停下滚动页面操作时才去请求数据。这种场景就适合用节流技术来实现。
原文地址:https://www.cnblogs.com/ichthyo-plu/p/11275965.html