JS防抖和节流
Posted ichthyo-plu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS防抖和节流相关的知识,希望对你有一定的参考价值。
对防抖和节流的一些理解,做一次记录。(之前项目中的需求是在输入框中输入内容之后,调接口返回值,然后不知道还有节流这波操作,然后就写了判断当鼠标失去焦点的时候调接口,后来大佬说可以使用节流来实现)
防抖和节流算起来应该属于性能优化的知识,但是处理不当或者是放任不管就容易引起浏览器卡死。就是在绑定scroll、resize这类事件时,当他发生时,被触发的频率非常高,间隔很近。如果事件中涉及到大量的位置计算、DOM操作、元素重绘等工作且这些工作无法在下一个scroll事件触发前完成,就会造成浏览器调帧。加之用户鼠标滚动往往时连续的,就会持续触发scroll事件导致调帧扩大、浏览器CPU使用率增加、用户体验受到影响。尤其时在涉及与后端的交互中,前端依赖于某中事件如resize、scroll,发送http请求,在这个过程中,如果不做防抖处理,那么在事件触发的一瞬间,就会有很多个请求发过去,增加了服务端的压力。
这个按钮只会在滚动到距离顶部一定位置的时候才会出现,那么现在抽象出这个功能需求 --- 监听滚动条事件,返回当前滚条和顶部的距离。
这个需求很简单,直接写:
1 function showTop () 2 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 3 console.log(‘滚动条位置:‘ + scrollTop); 4 5 window.onscroll = showTop
但是:在运行的时候会发现:这个函数的默认执行频率太高了!以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!
然而实际上并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以需要优化这种场景。
-
-
如果在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
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) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置
防抖也就实现了:定义即:
-
对于短时间内连续触发的事件(上面的滚动事件),
-
如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离。
其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。
效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
定时器方案
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));
-
搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
-
页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)
函数节流:使得一定时间内只触发一次函数,原理是通过判断是否到达一定时间来触发函数。
区别:函数节流不管事件触发多频繁,都会保证在规定的时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。比如在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次ajax请求,而不是在啊用户停下滚动页面操作时才去请求数据。这种场景就适合用节流技术来实现。
以上是关于JS防抖和节流的主要内容,如果未能解决你的问题,请参考以下文章