浅谈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 相关文章推荐
jQuery AnythingSlider滑动效果插件
Feb 07 Javascript
关于jquery动态增减控件的一些想法和小插件
Aug 01 Javascript
js获取某月的最后一天日期的简单实例
Jun 22 Javascript
DOM基础教程之使用DOM控制表单
Jan 20 Javascript
javascript匀速动画和缓冲动画详解
Oct 20 Javascript
JS中传递参数的几种不同方法比较
Jan 20 Javascript
前端分页功能的实现以及原理(jQuery)
Jan 22 Javascript
如何利用@angular/cli V6.0直接开发PWA应用详解
May 06 Javascript
vue3.0 CLI - 2.4 - 新组件 Forms.vue 中学习表单
Sep 14 Javascript
一个因@click.stop引发的bug的解决
Jan 08 Javascript
vue 实现动态路由的方法
Jul 06 Javascript
JS前端使用Canvas快速实现手势解锁特效
Sep 23 Javascript
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 快速生成 Flash 动画的方法
2007/03/06 PHP
PHP中error_reporting()函数的用法(修改PHP屏蔽错误)
2011/07/01 PHP
基于wordpress主题制作的具体实现步骤
2013/05/10 PHP
解析PHP高效率写法(详解原因)
2013/06/20 PHP
PHP-FPM运行状态的实时查看及监控详解
2016/11/18 PHP
PHP实现的防止跨站和xss攻击代码【来自阿里云】
2018/01/29 PHP
PHP-FPM 设置多pool及配置文件重写操作示例
2019/10/02 PHP
PHP发送邮件确认验证注册功能示例【修改别人邮件类】
2019/11/09 PHP
yii2.0框架实现上传excel文件后导入到数据库的方法示例
2020/04/13 PHP
IE8下关于querySelectorAll()的问题
2010/05/13 Javascript
Javascript匿名函数的一种应用 代码封装
2010/06/27 Javascript
异步javascript的原理和实现技巧介绍
2012/11/08 Javascript
js实现同一页面可多次调用的图片幻灯切换效果
2015/02/28 Javascript
JQuery中两个ul标签的li互相移动实现方法
2015/05/18 Javascript
JS去除空格和换行的正则表达式(推荐)
2016/06/14 Javascript
jQuery事件委托之Safari
2016/07/05 Javascript
node.js入门学习之url模块
2017/02/25 Javascript
Vue-cli 使用json server在本地模拟请求数据的示例代码
2017/11/02 Javascript
AngularJS实现的base64编码与解码功能示例
2018/05/17 Javascript
vue生命周期的探索
2019/04/03 Javascript
vue + any-touch实现一个iscroll 实现拖拽和滑动动画效果
2019/04/08 Javascript
Python实现简单的四则运算计算器
2016/11/02 Python
使用Python生成XML的方法实例
2017/03/21 Python
python实现员工管理系统
2018/01/11 Python
100行python代码实现跳一跳辅助程序
2018/01/15 Python
Python 字符串换行的多种方式
2018/09/06 Python
python微信聊天机器人改进版(定时或触发抓取天气预报、励志语录等,向好友推送)
2019/04/25 Python
pandas的qcut()方法详解
2019/07/06 Python
django admin后管定制-显示字段的实例
2020/03/11 Python
日本著名的平价时尚女性购物网站:Fifth
2016/08/24 全球购物
Nobody Denim官网:购买高级女士牛仔裤
2021/03/15 全球购物
高中毕业典礼演讲稿
2014/09/09 职场文书
三年级学生期末评语
2014/12/26 职场文书
素质教育学习心得体会
2016/01/19 职场文书
利用python做表格数据处理
2021/04/13 Python
浅谈Python numpy创建空数组的问题
2021/05/25 Python