浅谈JavaScript节流和防抖函数


Posted in Javascript onAugust 25, 2020

概念

节流函数

间隔固定的时间执行传入的方法

目的是防止函数执行的频率过快,影响性能.常见于跟滚动,鼠标移动事件绑定的功能.

防抖函数

对于接触过硬件的人也许更好理解,硬件按钮按下时,由于用户按住时间的长短不一,会多次触发电流的波动,加一个防抖函数就会只触发一次,防止了无意义的电流波动引起的问题.

按键防反跳(Debounce)为什么要去抖动呢?机械按键在按下时,并非按下就接触的很好,尤其是有簧片的机械开关,会在接触的瞬间反复的开合多次,直到开关状态完全改变。

应用在前端时,常见的场景是,输入框打字动作结束一段时间后再去触发查询/搜索/校验,而不是每打一个字都要去触发,造成无意义的ajax查询等,或者与调整窗口大小绑定的函数,其实只需要在最后窗口大小固定之后再去执行动作.

自己的实现

防抖函数

关键点在于每次触发时都清空延时函数的手柄,只有最后一次触发不会清空手柄,所以最后一次触发会等默认的1s后去执行debounce传入的参数函数f. debounce内部返回的闭包函数,是真正每次被调用触发的函数,不再是原本的f,所以这里的arguments取闭包函数环境变量中的arguments并在执行f时传给f,在setTimeout函数的外面取得.

let debounce = function(f, interval = 1000) {
 let handler = null;
 return function() {
  if (handler) {
  clearTimeout(handler);
  }
  let arg = arguments;
  handler = setTimeout(function() {
  f.apply(this, arg);
  clearTimeout(handler);
  }, interval)
 }
 }

应用:

let input = document.querySelector('#input');
 input.addEventListener('input', debounce(function(e) {
 console.log("您的输入是",e.target.value)
 }))

更高级的实现还会考虑到,以leading和trailing作为参数,起始先执行一次函数并消除后面的抖动,还是最后执行一下函数,消除前面的抖动,如同我这里的例子.后面分析loadash的防抖函数时会详细解析.

节流函数

let throttle = function(f,gap = 300){
  let lastCall = 0;
  return function(){
  let now = Date.now();
  let ellapsed = now - lastCall;
  if(ellapsed < gap){
   return
  }
  f.apply(this,arguments);
  lastCall = Date.now();
  }
 }

闭包函数在不断被调用的期间,去记录离上一次调用间隔的时间,如果间隔时间小于节流设置的时间则直接返回,不去执行真正被包裹的函数f.只有间隔时间大于了节流函数设置的时间gap,才调用f,并更新调用时间.

应用:

document.addEventListener('scroll', throttle(function (e) {
 // 判断是否滚动到底部的逻辑
 console.log(e,document.documentElement.scrollTop);
 }));

lodash源码分析

以上是对节流防抖函数最基础简单的实现,我们接下来分析一下lodash库中节流防抖函数的分析.

节流函数的使用

$(window).on('scroll', _.debounce(doSomething, 200));
function debounce(func, wait, options) {
 var lastArgs,
  lastThis,
  result,
  timerId,
  lastCallTime = 0,
  lastInvokeTime = 0,
  leading = false,
  maxWait = false,
  trailing = true;

 if (typeof func != 'function') {
  throw new TypeError(FUNC_ERROR_TEXT);
 }
 wait = wait || 0;
 if (isObject(options)) {
  leading = !!options.leading;
  maxWait = 'maxWait' in options && Math.max((options.maxWait) || 0, wait);
  trailing = 'trailing' in options ? !!options.trailing : trailing;
 }

 function invokeFunc(time) {
  var args = lastArgs,
  thisArg = lastThis;

  lastArgs = lastThis = undefined;
  lastInvokeTime = time;
  result = func.apply(thisArg, args);
  return result;
 }

 function leadingEdge(time) {
  console.log("leadingEdge setTimeout")
  // Reset any `maxWait` timer.
  lastInvokeTime = time;
  // Start the timer for the trailing edge.
  timerId = setTimeout(timerExpired, wait);
  // Invoke the leading edge.
  return leading ? invokeFunc(time) : result;
 }

 function remainingWait(time) {
  var timeSinceLastCall = time - lastCallTime,
  timeSinceLastInvoke = time - lastInvokeTime,
  result = wait - timeSinceLastCall;
  console.log("remainingWait",result)
  return maxWait === false ? result : Math.min(result, maxWait - timeSinceLastInvoke);
 }

 function shouldInvoke(time) {
  console.log("shouldInvoke")
  var timeSinceLastCall = time - lastCallTime,
  timeSinceLastInvoke = time - lastInvokeTime;
  console.log("time",time,"lastCallTime",lastCallTime,"timeSinceLastCall",timeSinceLastCall)
  console.log("time",time,"lastInvokeTime",lastInvokeTime,"timeSinceLastInvoke",timeSinceLastInvoke)
  console.log("should?",(!lastCallTime || (timeSinceLastCall >= wait) ||
  (timeSinceLastCall < 0) || (maxWait !== false && timeSinceLastInvoke >= maxWait)))
  // Either this is the first call, activity has stopped and we're at the
  // trailing edge, the system time has gone backwards and we're treating
  // it as the trailing edge, or we've hit the `maxWait` limit.
  return (!lastCallTime || (timeSinceLastCall >= wait) ||
  (timeSinceLastCall < 0) || (maxWait !== false && timeSinceLastInvoke >= maxWait));
 }

 function timerExpired() {
  console.log("timerExpired")
  var time = Date.now();
  if (shouldInvoke(time)) {
  return trailingEdge(time);
  }
  console.log("Restart the timer.",time,remainingWait(time))
  // Restart the timer.
  console.log("timerExpired setTimeout")
  timerId = setTimeout(timerExpired, remainingWait(time));
 }

 function trailingEdge(time) {
  clearTimeout(timerId);
  timerId = undefined;

  // Only invoke if we have `lastArgs` which means `func` has been
  // debounced at least once.
  console.log("trailing",trailing,"lastArgs",lastArgs)
  if (trailing && lastArgs) {
  return invokeFunc(time);
  }
  lastArgs = lastThis = undefined;
  return result;
 }

 function cancel() {
  if (timerId !== undefined) {
  clearTimeout(timerId);
  }
  lastCallTime = lastInvokeTime = 0;
  lastArgs = lastThis = timerId = undefined;
 }

 function flush() {
  return timerId === undefined ? result : trailingEdge(Date.now());
 }

 function debounced() {
  var time = Date.now(),
  isInvoking = shouldInvoke(time);
  console.log("time",time);
  console.log("isInvoking",isInvoking);
  lastArgs = arguments;
  lastThis = this;
  lastCallTime = time;

  if (isInvoking) {
  if (timerId === undefined) {
   return leadingEdge(lastCallTime);
  }
  // Handle invocations in a tight loop.
  clearTimeout(timerId);
  console.log("setTimeout")
  timerId = setTimeout(timerExpired, wait);
  return invokeFunc(lastCallTime);
  }
  return result;
 }
 debounced.cancel = cancel;
 debounced.flush = flush;
 return debounced;
 }

ref

https://css-tricks.com/debouncing-throttling-explained-examples/

https://github.com/lodash/lodash/blob/4.7.0/lodash.js#L9840

https://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/

以上就是浅谈JavaScript节流和防抖函数的详细内容,更多关于JavaScript节流和防抖函数的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
Convert Seconds To Hours
Jun 16 Javascript
Zero Clipboard js+swf实现的复制功能使用方法
Mar 07 Javascript
Javascript实现页面跳转的几种方式分享
Oct 26 Javascript
JS和函数式语言的三特性
Mar 05 Javascript
Javascript 构造函数详解
Oct 22 Javascript
JavaScript中的继承方式详解
Feb 11 Javascript
JavaScript编程中布尔对象的基本使用
Oct 25 Javascript
AngularJS表格详解及示例代码
Aug 17 Javascript
JS锚点的设置与使用方法
Sep 05 Javascript
ReactNative Image组件使用详解
Aug 07 Javascript
jQuery实现导航样式布局操作示例【可自定义样式布局】
Jul 24 jQuery
Vue.js 通过jQuery ajax获取数据实现更新后重新渲染页面的方法
Aug 09 jQuery
JS实现拖动模糊框特效
Aug 25 #Javascript
PHP读取远程txt文档到数组并实现遍历
Aug 25 #Javascript
JS轮播图的实现方法2
Aug 25 #Javascript
JS轮播图的实现方法
Aug 24 #Javascript
js 函数性能比较方法
Aug 24 #Javascript
JavaScript实现简单验证码
Aug 24 #Javascript
JavaScript经典案例之简易计算器
Aug 24 #Javascript
You might like
PHP下操作Linux消息队列完成进程间通信的方法
2010/07/24 PHP
php获取mysql数据库中的所有表名的代码
2011/04/23 PHP
php通过修改header强制图片下载的方法
2015/03/24 PHP
typecho插件编写教程(三):保存配置
2015/05/28 PHP
PHP Mysqli 常用代码集合
2016/11/12 PHP
thinkPHP模板引擎用法示例
2016/12/08 PHP
CI(CodeIgniter)框架实现图片上传的方法
2017/03/24 PHP
LAMP环境使用Composer安装Laravel的方法
2017/03/25 PHP
PHP实现权限管理功能示例
2017/09/22 PHP
PHP实现打包zip并下载功能
2018/06/12 PHP
javascript 原型继承介绍
2011/08/30 Javascript
JS如何将UTC格式时间转本地格式
2013/09/04 Javascript
js常用数组操作方法简明总结
2014/06/20 Javascript
javascript判断css3动画结束 css3动画结束的回调函数
2015/03/10 Javascript
解决JS内存泄露之js对象和dom对象互相引用问题
2017/06/25 Javascript
在vue中添加Echarts图表的基本使用教程
2017/11/22 Javascript
Angular模版驱动表单的使用总结
2018/05/05 Javascript
vue-music 使用better-scroll遇到轮播图不能自动轮播问题
2018/12/03 Javascript
python中threading超线程用法实例分析
2015/05/16 Python
python中getaddrinfo()基本用法实例分析
2015/06/28 Python
解决Python中字符串和数字拼接报错的方法
2016/10/23 Python
Python文件操作,open读写文件,追加文本内容实例
2016/12/14 Python
django之session与分页(实例讲解)
2017/11/13 Python
Flask-WTF表单的使用方法
2019/07/12 Python
Python 日期区间处理 (本周本月上周上月...)
2019/08/08 Python
Python实现隐马尔可夫模型的前向后向算法的示例代码
2019/12/31 Python
Python中itertools的用法详解
2020/02/07 Python
使用python-cv2实现视频的分解与合成的示例代码
2020/10/26 Python
英国Office鞋店德国网站:在线购买鞋子、靴子和运动鞋
2018/12/19 全球购物
应用电子专业学生的自我评价
2013/10/16 职场文书
法律专业实习鉴定
2013/12/22 职场文书
高中物理教学反思
2014/02/08 职场文书
课前三分钟演讲稿
2014/04/24 职场文书
2014机关干部学习“焦裕禄精神”思想汇报
2014/09/19 职场文书
心理健康教育培训研修感言
2015/11/18 职场文书
2016党员干部政治学习心得体会
2016/01/23 职场文书