浅谈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 相关文章推荐
js 颜色选择器(兼容firefox)
Mar 05 Javascript
关于extjs treepanel复选框选中父节点与子节点的问题
Apr 02 Javascript
关于JavaScript命名空间的一些心得
Jun 07 Javascript
javascript控制在光标位置插入文字适合表情的插入
Jun 09 Javascript
jQuery实现的Div窗口震动效果实例
Aug 07 Javascript
学习jQuey中的return false
Dec 18 Javascript
Jquery EasyUI Datagrid右键菜单实现方法
Dec 30 Javascript
VUE 实现滚动监听 导航栏置顶的方法
Sep 11 Javascript
记一次webapck4 配置文件无效的解决历程
Sep 19 Javascript
关于RxJS Subject的学习笔记
Dec 05 Javascript
微信小程序websocket聊天室的实现示例代码
Feb 12 Javascript
VUE注册全局组件和局部组件过程解析
Oct 10 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取整函数ceil,floo,round的用法及介绍
2013/08/31 PHP
Yii框架登录流程分析
2014/12/03 PHP
PHP中的Trait 特性及作用
2016/04/03 PHP
laravel学习教程之存取器
2016/07/30 PHP
php 如何禁用eval() 函数实例详解
2016/12/01 PHP
php合并数组并保留键值的实现方法
2018/03/12 PHP
实现连缀调用的map方法(prototype)
2009/08/05 Javascript
jquery tab插件制作实现代码
2010/06/22 Javascript
jquery ajax的success回调函数中实现按钮置灰倒计时
2013/11/19 Javascript
鼠标滚轮改变图片大小的示例代码
2013/11/20 Javascript
JavaScript中原型和原型链详解
2015/02/11 Javascript
js验证上传图片的方法
2015/05/12 Javascript
基于jquery实现日历签到功能
2020/09/11 Javascript
js控制TR的显示隐藏
2016/03/04 Javascript
javascript中call apply 与 bind方法详解
2016/03/10 Javascript
JavaScript实现form表单的多文件上传
2020/03/27 Javascript
JSON创建键值对(key是中文或者数字)方式详解
2017/08/24 Javascript
AngularJS中scope的绑定策略实例分析
2017/10/30 Javascript
angular json对象push到数组中的方法
2018/02/27 Javascript
基于vue-cli vue-router搭建底部导航栏移动前端项目
2018/02/28 Javascript
Vue 的双向绑定原理与用法揭秘
2020/05/06 Javascript
JS相册图片抖动放大展示效果的示例代码
2021/01/29 Javascript
你所不知道的Python奇技淫巧13招【实用】
2016/12/14 Python
利用python3随机生成中文字符的实现方法
2017/11/24 Python
TensorFlow实现Batch Normalization
2018/03/08 Python
在python中利用numpy求解多项式以及多项式拟合的方法
2019/07/03 Python
Django用户认证系统 Web请求中的认证解析
2019/08/02 Python
Android本地应用打开方法——通过html5写连接
2016/03/11 HTML / CSS
美国学校校服,儿童和婴儿服装:Cookie’s Kids
2016/10/14 全球购物
村干部培训方案
2014/05/02 职场文书
承诺书格式
2014/06/03 职场文书
单方离婚协议书范本(2014版)
2014/09/30 职场文书
医学专业大学生职业生涯规划书
2014/10/25 职场文书
岗位聘任报告
2015/03/02 职场文书
公司表扬稿范文
2015/05/05 职场文书
JavaScript数组 几个常用方法总结
2021/11/11 Javascript