JS防抖和节流

对防抖和节流的一些理解,作一次记录。(以前项目中的需求是在输入框中输入内容以后,调接口返回值,而后不知道还有节流这波操做,而后就写了判断当鼠标失去焦点的时候调接口,后来大佬说可使用节流来实现)前端

防抖和节流算起来应该属于性能优化的知识,可是处理不当或者是听任无论就容易引发浏览器卡死。就是在绑定scroll、resize这类事件时,当他发生时,被触发的频率很是高,间隔很近。若是事件中涉及到大量的位置计算、DOM操做、元素重绘等工做且这些工做没法在下一个scroll事件触发前完成,就会形成浏览器调帧。加之用户鼠标滚动每每时连续的,就会持续触发scroll事件致使调帧扩大、浏览器CPU使用率增长、用户体验受到影响。尤为时在涉及与后端的交互中,前端依赖于某中事件如resize、scroll,发送http请求,在这个过程当中,若是不作防抖处理,那么在事件触发的一瞬间,就会有不少个请求发过去,增长了服务端的压力。ajax

1.从滚动条监听的例子提及

先说一个常见的功能,不少网站会提供一个按钮:用于返回顶部。chrome

这个按钮只会在滚动到距离顶部必定位置的时候才会出现,那么如今抽象出这个功能需求 --- 监听滚动条事件,返回当前滚条和顶部的距离。后端

这个需求很简单,直接写:浏览器

1 function showTop  () {
2     var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
3   console.log('滚动条位置:' + scrollTop);
4 }
5 window.onscroll  = showTop
View Code

可是:在运行的时候会发现:这个函数的默认执行频率过高了!以chrome为例,咱们能够点击选中一个页面的滚动条,而后点击一次键盘的【向下方向键】,会发现函数执行了8-9次性能优化

然而实际上并不须要如此高频的反馈,毕竟浏览器的性能是有限的,不该该浪费在这里,因此须要优化这种场景。闭包

2.防抖

基于上述的场景,首先提出第一种思路:在第一次触发事件时,不当即执行函数,而是给出一个期限值:300msapp

  • 若是在300ms内没有再次触发滚动事件,那么就执行函数。dom

  • 若是在300ms内再次触发滚动事件,那么当前的即便取消,从新开始计时。ide

效果就是:若是在短期内大量触发赞成事件,只会执行一次函数。

实现:既然前面都提到了计时,那实现的关键就在于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 }
View Code

固然 上述代码是为了贴合思路,方便理解。写完会发现其实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) // 为了方便观察效果咱们取个大点的间断值,实际使用根据须要来配置
View Code

此时会发现,必须在中止滚动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) 
View Code

运行以上代码的结果:

若是一直拖着滚动条进行滚动,那么会以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));
View Code

时间戳+定时器

 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));
View Code

4.其余应用场景举例

讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:

  1. 搜索框input事件,例如要支持输入实时搜索可使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当作用户输入完成,而后开始搜索,具体使用哪一种方案要看业务需求。

  2. 页面resize事件,常见于须要作页面适配的时候。须要根据最终呈现的页面状况进行dom渲染(这种情形通常是使用防抖,由于只须要判断最后一次的变化状况)

5.总结

函数防抖:将几回操做合并为一个操做进行。原理是维护一个计时器,规定在delay时间后触发函数,可是在delay内再次触发的话,就会取消以前的计时器而从新设置。这样一来。只有最后一次操做能被触发。

函数节流:使得必定时间内只触发一次函数,原理是经过判断是否到达必定时间来触发函数。

区别:函数节流无论事件触发多频繁,都会保证在规定的时间内必定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。好比在页面的无限加载场景下,须要用户在滚动页面时,每隔一段时间发一次ajax请求,而不是在啊用户停下滚动页面操做时才去请求数据。这种场景就适合用节流技术来实现。