浅谈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 相关文章推荐
ExtJS的FieldSet的column列布局
Nov 20 Javascript
基于jquery的跨域调用文件
Nov 19 Javascript
javascript数组去掉重复
May 12 Javascript
Prototype源码浅析 String部分(三)之HTML字符串处理
Jan 15 Javascript
简单易用的倒计时js代码
Aug 04 Javascript
js实现Form栏显示全格式时间时钟效果代码
Aug 19 Javascript
uploadify多文件上传参数设置技巧
Nov 16 Javascript
关于react-router的几种配置方式详解
Jul 24 Javascript
JavaScript设计模式之装饰者模式定义与应用示例
Jul 25 Javascript
vue最简单的前后端交互示例详解
Oct 11 Javascript
使用jQuery如何写一个含验证码的登录界面
May 13 jQuery
Vue 数据响应式相关总结
Jan 28 Vue.js
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基于自定义函数生成笛卡尔积的方法示例
2017/09/30 PHP
客户端js性能优化小技巧整理
2013/11/05 Javascript
jQuery 获取和设置select下拉框的值实现代码
2013/11/08 Javascript
Javascript 遍历页面text控件详解
2014/01/06 Javascript
Jquery获得控件值的三种方法总结
2014/02/13 Javascript
用JS在浏览器中创建下载文件
2014/03/05 Javascript
使用jquery选择器如何获取父级元素、同级元素、子元素
2014/05/14 Javascript
什么是 AngularJS?AngularJS简介
2014/12/06 Javascript
node.js中的fs.symlink方法使用说明
2014/12/15 Javascript
js实现仿QQ秀换装效果的方法
2015/03/04 Javascript
js插件设置innerHTML时在IE8下提示“未知运行时错误”解决方法
2015/04/25 Javascript
jQuery滚动新闻实现代码
2016/06/26 Javascript
js实现五星评价功能
2017/03/08 Javascript
Javascript中的作用域及块级作用域
2017/12/08 Javascript
javascript代码优化的8点总结
2018/01/29 Javascript
vue请求本地自己编写的json文件的方法
2019/04/25 Javascript
Vue表单控件数据绑定方法详解
2020/02/05 Javascript
javascript使用Blob对象实现的下载文件操作示例
2020/04/18 Javascript
vuex中store存储store.commit和store.dispatch的用法
2020/07/24 Javascript
vue实现动态表格提交参数动态生成控件的操作
2020/11/09 Javascript
在漏洞利用Python代码真的很爽
2007/08/26 Python
进一步探究Python中的正则表达式
2015/04/28 Python
Python3遍历目录树实现方法
2015/05/22 Python
Python读取mat文件,并保存为pickle格式的方法
2018/10/23 Python
解决pyinstaller打包发布后的exe文件打开控制台闪退的问题
2019/06/21 Python
python机器学习库scikit-learn:SVR的基本应用
2019/06/26 Python
Python数据可视化:幂律分布实例详解
2019/12/07 Python
使用pandas读取表格数据并进行单行数据拼接的详细教程
2021/03/03 Python
css3 边框、背景、文本效果的实现代码
2018/03/21 HTML / CSS
利用HTML5+css3+jquery+weui实现仿微信聊天界面功能
2018/01/08 HTML / CSS
HTML5梦幻之旅——炫丽的流星雨效果实现过程
2013/08/06 HTML / CSS
什么是跨站脚本攻击
2014/12/11 面试题
经济与贸易专业应届生求职信
2013/11/19 职场文书
2014年国培研修感言
2014/03/09 职场文书
2015年业务员工作总结范文
2015/04/07 职场文书
MySQL中TIMESTAMP类型返回日期时间数据中带有T的解决
2022/12/24 MySQL