详细分析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 相关文章推荐
DWR Ext 加载数据
Mar 22 Javascript
Jsonp 跨域的原理以及Jquery的解决方案
Jun 27 Javascript
微信浏览器内置JavaScript对象WeixinJSBridge使用实例
May 25 Javascript
js中函数声明与函数表达式
Jun 03 Javascript
深入解析JavaScript中的数字对象与字符串对象
Oct 21 Javascript
深入浅析Node.js 事件循环
Dec 20 Javascript
bootstrap网页框架的使用方法
May 10 Javascript
JavaScript实现左右下拉框动态增删示例
Mar 09 Javascript
Vue2递归组件实现树形菜单
Apr 10 Javascript
javascript和php使用ajax通信传递JSON的实例
Aug 21 Javascript
vue3.0 加载json的方法(非ajax)
Oct 26 Javascript
输入框跟随文字内容适配宽实现示例
Aug 14 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
PHP编程中字符串处理的5个技巧小结
2007/11/13 PHP
php feof用来识别文件末尾字符的方法
2010/08/01 PHP
win7下memCache的安装过程(具体操作步骤)
2013/06/28 PHP
php生成无限栏目树
2017/03/16 PHP
thinkPHP框架整合tcpdf插件操作示例
2018/08/07 PHP
javascript 面向对象全新理练之继承与多态
2009/12/03 Javascript
jQuery动态添加删除select项(实现代码)
2013/09/03 Javascript
jquery根据name属性查找的小例子
2013/11/21 Javascript
Jquery 过滤器(first,last,not,even,odd)的使用
2014/01/22 Javascript
node.js中的console用法总结
2014/12/15 Javascript
javascript文本框内输入文字倒计数的方法
2015/02/24 Javascript
jquery分页插件jquery.pagination.js使用方法解析
2016/04/01 Javascript
ES6新数据结构Map功能与用法示例
2017/03/31 Javascript
webpack4 css打包压缩问题的解决
2018/05/18 Javascript
Layui数据表格之获取表格中所有的数据方法
2018/08/20 Javascript
node.js使用redis储存session的方法
2018/09/26 Javascript
vue在index.html中引入静态文件不生效问题及解决方法
2019/04/29 Javascript
基于ajax及jQuery实现局部刷新过程解析
2020/09/12 jQuery
JavaScript数组常用的增删改查与其他属性详解
2020/10/13 Javascript
浅谈python内置变量-reversed(seq)
2017/06/21 Python
Python2.7+pytesser实现简单验证码的识别方法
2017/12/29 Python
python opencv之SURF算法示例
2018/02/24 Python
centos6.8安装python3.7无法import _ssl的解决方法
2018/09/17 Python
Python列表原理与用法详解【创建、元素增加、删除、访问、计数、切片、遍历等】
2019/10/30 Python
Python tkinter常用操作代码实例
2020/01/03 Python
tensorflow 报错unitialized value的解决方法
2020/02/06 Python
几款Python编译器比较与推荐(小结)
2020/10/15 Python
BIBLOO捷克:购买女装、男装、童装、鞋和配件
2017/01/27 全球购物
日本著名的服饰鞋帽综合类购物网站:MAGASEEK
2019/01/09 全球购物
加拿大著名的奢侈品购物网站:SSENSE(支持中文)
2020/06/25 全球购物
幼儿园中班新学期寄语
2014/01/18 职场文书
学校感恩教育活动总结
2014/07/07 职场文书
教师批评与自我批评心得体会
2014/10/16 职场文书
安全生产奖惩制度
2015/08/06 职场文书
会计手工模拟做账心得体会
2016/01/22 职场文书
Three.js实现雪糕地球的使用示例详解
2022/07/07 Javascript