详细分析JS函数去抖和节流


Posted in Javascript onDecember 05, 2017

本篇内容从节流和去抖的概念基础知识讲起,对JS函数做了详细的分析,一起来看下:

1、什么是节流和去抖?

节流。就是拧紧水龙头让水少流一点,但是不是不让水流了。想象一下在现实生活中有时候我们需要接一桶水,接水的同时不想一直站在那等着,可能要离开一会去干一点别的事请,让水差不多流满一桶水的时候再回来,这个时候,不能把水龙头开的太大,不然还没回来水就已经满了,浪费了好多水,这时候就需要节流,让自己回来的时候水差不多满了。那在JS里有没有这种情况呢,典型的场景是图片懒加载监听页面的scoll事件,或者监听鼠标的mousemove事件,这些事件对应的处理方法相当于水,由于scroll和mousemove在鼠标移动的时候会被浏览器频繁的触发,会导致对应的事件也会被频繁的触发(水流的太快了),这样就会造成很大的浏览器资源开销,而且好多中间的处理是不必要的,这样就会造成浏览器卡顿的现象,这时候就需要节流,如何节流呢?我们无法做到让浏览器不去触发对应的事件,但是可以做到让处理事件的方法执行频率减少,从而减少对应的处理开销。

去抖。最早接触这个词应该是在高中物理里面学到的,有时候开关在在真正闭合之前可能会发生一些抖动现象,如果抖动的明显的话,对应的小灯泡可能会闪烁,把灯泡闪坏了不重要,万一把眼睛再给闪坏了可就麻烦了,这个时候就有去抖电路的出现。而在我们的页面里,也有这种情况,假设我们的一个输入框,输入内容的同时可能会去后台查询对应的联想 词,如果用户输入的同时,频繁的触发input事件,然后频繁的向后抬发送请,那么直到用户输入完成时,之前的请求都应该是多余的,假设网络慢一点,后台返回的数据比较慢,那么显示的联想词可能会出现频繁的变换,直到最后的一个请求返回。这个时候就可以在一定时间内监听是否再次输入,如果没有再次输入则认为本次输入完成,发送请求,否则就是判定用户仍在输入,不发送请求。

去抖和节流是不同的,因为节流虽然中间的处理函数被限制了,但是只是减少了频率,而去抖则把中间的处理函数全部过滤掉了,只执行规判定时间内的最后一个事件。

2、JS实现。

前面BB了这么多,感谢你耐心的看到这里,接下来我们来自己动手看看如何实现节流和去抖。

节流:

/** 实现思路:
  ** 参数需要一个执行的频率,和一个对应的处理函数,
  ** 内部需要一个lastTime 变量记录上一次执行的时间
  **/
  function throttle (func, wait) {
   let lastTime = null
// 为了避免每次调用lastTime都被清空,利用js的闭包返回一个function确保不生命全局变量也可以
   return function () {
    let now = new Date()
    // 如果上次执行的时间和这次触发的时间大于一个执行周期,则执行
    if (now - lastTime - wait > 0) {
     func()
     lastTime = now
    }
   }
  }

再看如何调用:

// 由于闭包的存在,调用会不一样
let throttleRun = throttle(() => {
  console.log(123)
}, 400)
window.addEventListener('scroll', throttleRun)

这时候f疯狂的滚动页面,会发现会400ms打印一个123,而没有节流的话会不断地打印, 你可以改变wait参数去感受下不同。

但是到这里,我们的节流方法是不完善的,因为我们的方法没有获取事件发生时的this对象,而且由于我们的方法简单粗暴的通过判断这次触发的时间和上次执行时间的间隔来决定是否执行回调,这样就会造成最后一次触发无法执行,或者用户出发的间隔确实很短,也无法执行,造成了误杀,所以需要对方法进行完善。

function throttle (func, wait) {
   let lastTime = null
   let timeout
   return function () {
    let context = this
    let now = new Date()
    // 如果上次执行的时间和这次触发的时间大于一个执行周期,则执行
    if (now - lastTime - wait > 0) {
     // 如果之前有了定时任务则清除
     if (timeout) {
      clearTimeout(timeout)
      timeout = null
     }
     func.apply(context, arguments)
     lastTime = now
    } else if (!timeout) {
     timeout = setTimeout(() => {
      // 改变执行上下文环境
      func.apply(context, arguments)
     }, wait)
    }
   }
  }

这样我们的方法就相对完善了,调用方法和之前相同。

去抖:

去抖的方法,和节流思路一致,但是只有在抖动被判定结束后,方法才会得到执行。

debounce (func, wait) {
   let lastTime = null
   let timeout
   return function () {
    let context = this
    let now = new Date()
    // 判定不是一次抖动
    if (now - lastTime - wait > 0) {
     setTimeout(() => {
      func.apply(context, arguments)
     }, wait)
    } else {
     if (timeout) {
      clearTimeout(timeout)
      timeout = null
     }
     timeout = setTimeout(() => {
      func.apply(context, arguments)
     }, wait)
    }
    // 注意这里lastTime是上次的触发时间
    lastTime = now
   }
  }

这时候按照之前同样的方式调用,会发现无论怎么疯狂的滚动窗口,只有停止滚动时,才会执行对应的事件。

去抖和节流已经有很多成熟的js进行了实现,其大致思路基本是这样的。

我们再给大家分享一下网友的实现方法的代码:

方法一

1.这种实现方式的思路很好理解:设置一个一间隔时间,比如50毫秒,以此时间为基准设置定时器,当第一次触发事件到第二次触发事件间隔小于50毫秒时,清除这个定时器,并设置一个新的定时器,以此类推,直到有一次事件触发后50毫秒内没有重复触发。代码如下:

function debounce(method){ 
 clearTimeout(method.timer); 
 method.timer=setTimeout(function(){ 
  method(); 
 },50); 
}

这种设计方式有一个问题:本来应该多次触发的事件,可能最终只会发生一次。具体来说,一个循序渐进的滚动事件,如果用户滚动太快速,或者程序设置的函数节流间隔时间太长,那么最终滚动事件会呈现为一个很突然的跳跃事件,中间过程都被节流截掉了。这个例子举的有点夸张了,不过使用这种方式进行节流最终是会明显感受到程序比不节流的时候“更突兀”,这对于用户体验是很差的。有一种弥补这种缺陷的设计思路。

方法二

2.第二种实现方式的思路与第一种稍有差别:设置一个间隔时间,比如50毫秒,以此时间为基准稳定分隔事件触发情况,也就是说100毫秒内连续触发多次事件,也只会按照50毫秒一次稳定分隔执行。代码如下:

var oldTime=new Date().getTime(); 
var delay=50; 
function throttle1(method){ 
 var curTime=new Date().getTime(); 
 if(curTime-oldTime>=delay){ 
  oldTime=curTime; 
  method(); 
 } 
}

相比于第一种方法,第二种方法也许会比第一种方法执行更多次(有时候意味着更多次请求后台,即更多的流量),但是却很好的解决了第一种方法清除中间过程的缺陷。因此在具体场景应根据情况择优决定使用哪种方法。

对于方法二,我们再提供另一种同样功能的写法:

var timer=undefined,delay=50; 
function throttle2(method){ 
 if(timer){ 
  return ; 
 } 
 method(); 
 timer=setTimeout(function(){ 
  timer=undefined; 
 },delay); 
}
Javascript 相关文章推荐
基于jquery的inputlimiter 实现字数限制功能
May 30 Javascript
自动最大化窗口的Javascript代码
May 22 Javascript
做好七件事帮你提升jQuery的性能
Feb 06 Javascript
AngularJS 指令详细介绍
Jul 27 Javascript
原生Javascript和jQuery做轮播图简单例子
Oct 11 Javascript
值得分享的Bootstrap Table使用教程
Nov 23 Javascript
javascript数组去重方法分析
Dec 15 Javascript
Bootstrap Table 删除和批量删除
Sep 22 Javascript
ajax前台后台跨域请求处理方式
Feb 08 Javascript
webpack4.x打包过程详解
Jul 18 Javascript
node.js 微信开发之定时获取access_token
Feb 07 Javascript
vue npm install 安装某个指定的版本操作
Aug 11 Javascript
微信小程序实现点击按钮修改字体颜色功能【附demo源码下载】
Dec 05 #Javascript
微信小程序实现动态改变view标签宽度和高度的方法【附demo源码下载】
Dec 05 #Javascript
vue+iview写个弹框的示例代码
Dec 05 #Javascript
Node.js创建Web、TCP服务器
Dec 05 #Javascript
实例讲解javascript实现异步图片上传方法
Dec 05 #Javascript
jquery如何实现点击空白处隐藏元素
Dec 05 #jQuery
jQuery实现验证表单密码一致性及正则表达式验证邮箱、手机号的方法
Dec 05 #jQuery
You might like
为什么那些咖啡爱好者大多看不上连锁咖啡店?
2021/03/06 咖啡文化
PHP实现Socket服务器的代码
2008/04/03 PHP
在PHP中运行Linux命令并启动SSH服务的例子
2014/06/12 PHP
php使用递归函数实现数字累加的方法
2015/03/16 PHP
php命令行模式代码实例详解
2021/02/26 PHP
Array对象方法参考
2006/10/03 Javascript
javascript操作cookie_获取与修改代码
2009/05/21 Javascript
Jquery 动态循环输出表格具体方法
2013/11/23 Javascript
如何调试异步加载页面里包含的js文件
2014/10/30 Javascript
JavaScript中String.prototype用法实例
2015/05/20 Javascript
js实现文字截断功能
2016/09/14 Javascript
Angular2 父子组件数据通信实例
2017/06/22 Javascript
vue.js学习之vue-cli定制脚手架详解
2017/07/02 Javascript
Vue组件的使用教程详解
2018/01/05 Javascript
利用CDN加速react webpack打包后的文件详解
2018/02/22 Javascript
[04:48]DOTA2亚洲邀请赛林书豪为VGJ加油
2017/04/01 DOTA
python的正则表达式re模块的常用方法
2013/03/09 Python
Python占用的内存优化教程
2019/07/28 Python
如何修复使用 Python ORM 工具 SQLAlchemy 时的常见陷阱
2019/11/19 Python
Python求解正态分布置信区间教程
2019/11/20 Python
最新版 Windows10上安装Python 3.8.5的步骤详解
2020/11/28 Python
国际性能运动服装品牌:Dare 2b
2018/07/27 全球购物
面向对象概念面试题(.NET)
2016/11/04 面试题
LINUX下线程,GDI类的解释
2012/04/17 面试题
Java程序开发中如何应用线程
2016/03/03 面试题
商场经理竞聘演讲稿
2014/01/01 职场文书
GMP办公室主任岗位职责
2014/03/14 职场文书
《观舞记》教学反思
2014/04/16 职场文书
读书活动总结范文
2014/04/26 职场文书
信息管理与信息系统专业求职信
2014/06/21 职场文书
大学专科求职信
2014/07/02 职场文书
作风建设整改方案
2014/10/27 职场文书
申报优秀教师材料
2014/12/16 职场文书
我们的节日端午节活动总结
2015/02/11 职场文书
Node实现搜索框进行模糊查询
2021/06/28 Javascript
Redis特殊数据类型bitmap位图
2022/06/01 Redis