浅谈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 相关文章推荐
JavaScript中的console.trace()函数介绍
Dec 29 Javascript
jQuery实现感应鼠标动画效果自动伸长的输入框实例
Feb 24 Javascript
Angularjs的ng-repeat中去除重复数据的方法
Aug 05 Javascript
基于Marquee.js插件实现的跑马灯效果示例
Jan 25 Javascript
Angular4学习教程之HTML属性绑定的方法
Jan 04 Javascript
AngularJS使用Filter自定义过滤器控制ng-repeat去除重复功能示例
Apr 21 Javascript
浅谈Redux中间件的实践
Jul 27 Javascript
Vue一次性简洁明了引入所有公共组件的方法
Nov 28 Javascript
JavaScript栈和队列相关操作与实现方法详解
Dec 07 Javascript
jQuery实现的简单歌词滚动功能示例
Jan 07 jQuery
layui输入框只允许输入中文且判断长度的例子
Sep 18 Javascript
javascript贪吃蛇游戏设计与实现
Sep 17 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中flush()、ob_flush()、ob_end_flush()的区别介绍
2013/02/17 PHP
PHP实现根据时间戳获取周几的方法
2016/02/26 PHP
将PHP的session数据存储到数据库中的代码实例
2016/06/24 PHP
PHP+AJAX 投票器功能
2017/11/11 PHP
Js动态创建div
2008/09/25 Javascript
理解Javascript_15_作用域分配与变量访问规则,再送个闭包
2010/10/20 Javascript
由JavaScript中call()方法引发的对面向对象继承机制call的思考
2011/09/12 Javascript
js jq 单击和双击区分示例介绍
2013/11/05 Javascript
javascript使用百度地图api和html5特性获取浏览器位置
2014/01/10 Javascript
浅析javascript操作 cookie对象
2014/12/26 Javascript
深入探寻seajs的模块化与加载方式
2015/04/14 Javascript
完美兼容多浏览器的js判断图片路径代码汇总
2015/04/17 Javascript
jquery 判断selection range 是否在容器中的简单实例
2016/08/02 Javascript
JS触发服务器控件的单击事件(详解)
2016/08/06 Javascript
jQuery组件easyui对话框实现代码
2016/08/25 Javascript
Bootstrap CSS布局之按钮
2016/12/17 Javascript
详解Vue方法与事件
2017/03/09 Javascript
JS传参及动态修改页面布局
2017/04/13 Javascript
AngularJS之ionic 框架下实现 Localstorage本地存储
2017/04/22 Javascript
在vue-cli中组件通信的方法
2017/12/16 Javascript
深入理解JavaScript的async/await
2018/08/05 Javascript
小程序input数据双向绑定实现方法
2019/10/17 Javascript
Vue.js watch监视属性知识点总结
2019/11/11 Javascript
微信小程序调用wx.getImageInfo遇到的坑解决
2020/05/31 Javascript
Element Cascader 级联选择器的使用示例
2020/07/27 Javascript
python将秒数转化为时间格式的实例
2018/09/16 Python
Python中安装easy_install的方法
2018/11/18 Python
找Python安装目录,设置环境路径以及在命令行运行python脚本实例
2020/03/09 Python
HTML5新增的8类INPUT输入类型介绍
2015/07/06 HTML / CSS
html5+css3进度条倒计时动画特效代码【推荐】
2016/03/08 HTML / CSS
销售类个人求职信范文
2013/09/25 职场文书
菜篮子工程实施方案
2014/03/08 职场文书
春节联欢会策划方案
2014/05/16 职场文书
大学国际贸易专业自荐信
2014/06/05 职场文书
浅析Python中的套接字编程
2021/06/22 Python
python数据可视化JupyterLab实用扩展程序Mito
2021/11/20 Python