详细分析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 LigerUI 插件介绍及使用之ligerDrag和ligerResizable示例代码打包
Apr 06 Javascript
javascript中方便增删改cookie的一个类
Oct 11 Javascript
jQuery.validate 常用方法及需要注意的问题
Mar 20 Javascript
基于jquery实现后台左侧菜单点击上下滑动显示
Apr 11 Javascript
7个JS基础知识总结
Mar 05 Javascript
jQuery实现的感应鼠标悬停图片色彩渐显效果
Mar 03 Javascript
基于jQuery实现仿QQ空间送礼物功能代码
May 24 Javascript
JS实现HTML表格排序功能
Aug 05 Javascript
Vue 2.0中生命周期与钩子函数的一些理解
May 09 Javascript
JS动画实现回调地狱promise的实例代码详解
Nov 08 Javascript
validform表单验证的实现方法
Mar 08 Javascript
js实现的在本地预览图片功能示例
Nov 09 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
CodeIgniter扩展核心类实例详解
2016/01/20 PHP
visual studio code 调试php方法(图文详解)
2017/09/15 PHP
html中table数据排序的js代码
2011/08/09 Javascript
js转化毫秒为时间格式代码
2014/04/10 Javascript
js通过location.search来获取页面传来的参数
2014/09/11 Javascript
jQuery+ajax实现动态执行脚本的方法
2015/01/27 Javascript
Backbone.js 0.9.2 源码注释中文翻译版
2015/06/25 Javascript
javascript实现很浪漫的气泡冒出特效
2020/09/05 Javascript
浅谈JS中json数据的处理
2016/06/30 Javascript
JavaScript实现左右下拉框动态增删示例
2017/03/09 Javascript
AngularJS学习笔记之表单验证功能实例详解
2017/07/06 Javascript
在vue-cli的组件模板里使用font-awesome的两种方法
2018/09/28 Javascript
详解Vue.js iview实现树形权限表(可扩展表)
2018/09/30 Javascript
Element Rate 评分的使用方法
2020/07/27 Javascript
微信小程序基于高德地图API实现天气组件(动态效果)
2020/10/22 Javascript
[01:14:05]《加油DOTA》第四期
2014/08/25 DOTA
Python实现远程调用MetaSploit的方法
2014/08/22 Python
Python实现列表删除重复元素的三种常用方法分析
2017/11/24 Python
Django 生成登陆验证码代码分享
2017/12/12 Python
Python selenium实现微博自动登录的示例代码
2018/05/16 Python
django数据关系一对多、多对多模型、自关联的建立
2019/07/24 Python
python config文件的读写操作示例
2019/09/27 Python
django框架ModelForm组件用法详解
2019/12/11 Python
flask的orm框架SQLAlchemy查询实现解析
2019/12/12 Python
利用python下载scihub成文献为PDF操作
2020/07/09 Python
Web前端绘制0.5像素的几种方法
2017/08/11 HTML / CSS
css3个性化字体_动力节点Java学院整理
2017/07/12 HTML / CSS
CSS3中box-shadow的用法介绍
2015/07/15 HTML / CSS
阿拉伯书店:Jamalon
2019/07/24 全球购物
德国家用电器购物网站:Premiumshop24
2019/08/22 全球购物
美国名表在线商城:Ashford(支持中文)
2019/09/24 全球购物
门卫人员岗位职责
2013/12/24 职场文书
大学毕业生个人自荐信范文
2014/01/08 职场文书
学校安全生产承诺书
2014/05/23 职场文书
门球健将观后感
2015/06/16 职场文书
mysql批量新增和存储的方法实例
2021/04/07 MySQL