函数去抖(debounce)& 函数节流(throttle)总结
1. 什么是函数去抖 & 函数节流
让某个函数在一定 事件间隔条件(去抖debounce) 或 时间间隔条件(节流throttle) 下才会去执行,避免快速多次执行函数(操作DOM,加载资源等等)给内存带来大量的消耗从而一定程度上降低性能问题.
debounce: 当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。
throttle:预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。
debounce使用场景
scroll
事件(资源的加载)mousemove
事件(拖拽)resize
事件(响应式布局样式)keyup
事件(输入框文字停止打字后才进行校验)
debounce电梯:
假设你正在准备乘坐电梯,并且电梯门准备关上然后上升的时候,你的同事来了,出于礼貌,我们需要停止电梯的关闭,让同事进入.
假设源源不断的有同事进来的话,电梯就需要处于一种待机的状态,一直等待人员的进入,直到没有新的同事进入或者说电梯满了,这个时候,电梯才能运行.另外,同事的进入需要在电梯门的关闭之前,否则的话,就只能等下一趟了。
throttle使用场景
click
事件(不停快速点击按钮,减少触发频次)scroll
事件(返回顶部按钮出现\隐藏事件触发)keyup
事件(输入框文字与显示栏内容复制同步)- 减少发送ajax请求,降低请求频率
throttle电梯:
throttle电梯不想debounce电梯一样会无限的等待,而是我们设定一个时间,例如10s,那么10s内,其他的人可以不断的进入电梯
但是,一旦10s过去了,那么无论如何,电梯都会进入运行的状态。换成图示,我们可以这么理解:
2. 实现方法&应用
首先是自己写的各自简易的实现,然后对比理解Lodash实现的复杂版本。看完你会发现节流本质上是去抖的一种特殊实现。
a. 简单实现
debounce
//html <button id="btn">click</button> <div id="display"></div>
.js /** * 防反跳。fn函数在最后一次调用时刻的delay毫秒之后执行! * @param fn 执行函数 * @param delay 时间间隔 * @param isImmediate 为true,debounce会在delay时间间隔的开始时立即调用这个函数 * @returns {Function} */ function debounce(fn, delay, isImmediate) { var timer = null; //初始化timer,作为计时清除依据 return function() { var context = this; //获取函数所在作用域this var args = arguments; //取得传入参数 clearTimeout(timer); if(isImmediate && timer === null) { //时间间隔外立即执行 fn.apply(context,args); timer = 0; return; } timer = setTimeout(function() { fn.apply(context,args); timer = null; }, delay); } } /* 方法执行e.g. */ var btn = document.getElementById('btn'); var el = document.getElementById('display'); var init = 0; btn.addEventListener('click', debounce(function() { init++; el.innerText = init; }, 1000,true));
⇒ Demo
说明:
这里实现了一个有去抖功能的计数器。该函数接收三个参数,分别是要执行的函数fn、事件完成周期时间间隔delay(即事件间隔多少时间内不再重复触发)以及是否在触发周期内立即执行isImmediate。
需要注意的是要给执行函数绑定一个调用函数的上下文以及对应传入的参数。
示例中对click事件进行了去抖,间隔时间为1000毫秒,为立即触发方式,当不停点击按钮时,第一次为立即触发,之后直到最后一次点击事件结束间隔delay秒后开始执行加1操作。
throttle
/** * 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔delay毫秒调用一次该函数 * @param fn 执行函数 * @param delay 时间间隔 * @returns {Function} */ function throttle(fn, delay) { var timer = null; var timeStamp = new Date(); return function() { var context = this; //获取函数所在作用域this var args = arguments; //取得传入参数 if(new Date()-timeStamp>delay){ timeStamp = new Date(); timer = setTimeout(function(){ fn.apply(context,args); },delay); } } } /* 方法执行 */ var btn = document.getElementById('btn'); var el = document.getElementById('display'); var init = 0; btn.addEventListener('click', throttle(function() { init++; el.innerText = init; }, 1000));
说明:
这里实现了一个简易的有去节流功能的计数器。该函数接收两个参数,分别是要执行的函数fn、事件完成周期时间间隔delay(即事件间隔多少时间内不再重复触发)。需要注意的是要给执行函数绑定一个调用函数的上下文以及对应传入的参数,以及在闭包外层的timeStamp时间记录戳,用于判断事件的时间间隔。示例中对click事件进行了节流,间隔时间为1000毫秒,不停点击按钮,计数器会间隔1秒时间进行加1操作。
缺点:
没有控制事件的头尾选项,即没有控制是否在连续事件的一开始及最终位置是否需要执行。(参考underscore弥补)
⇒ Demo