为何会有去抖和节流这类工具函数?javascript
在用户和前端页面的交互过程当中,不少操做的触发频率很是高,好比鼠标移动 mousemove 事件, 滚动条滑动 scroll 事件, 输入框 input 事件, 键盘 keyup 事件,浏览器窗口 resize 事件。html
在以上事件上绑定回调函数,若是回调函数是一些须要大量计算、消耗内存、HTTP 请求、DOM 操做等,那么应用的性能和体验就会很是的差。前端
去抖和节流函数的根据思想就是,减小高频率事件处理函数 handler 的执行频率(注意是事件处理函数,不是事件回调函数),将屡次事件的回调合并成一个回调来执行,从而优化性能。java
去抖(debounce),也叫防抖,那抖动指的是什么呢?抖动意味着操做的不稳定性,你能够理解成躁动症,安静不下来~防抖的含义即是为了防止抖动形成的结果不许确,等到稳定的时候再处理结果。ajax
好比在输入事件,鼠标移动,滚动条滑动,键盘敲击事件中,等到中止事件触发,频率稳定为零后,才开始执行回调函数,也就是所谓的没有抖动后处理。数组
我的总结:去抖,就是事件触发频率稳定后,才开始执行回调函数, 一连串的事件触发,但只进行一次事件处理。浏览器
频率就是单位时间触发的次数,若是单位时间内,事件触发超过一次,就只执行最后一次,若是单位时间内没有触发超过一次,那就正常执行。去抖分为延迟执行和当即执行两种思路。闭包
看一个简单版的去抖函数延迟执行实现:app
<div>
输入框: <input type="text" id="exampleInput">
</div>
<script> window.onload = function() { var inputEl = document.getElementById("exampleInput"); inputEl.oninput = debounce(ajax); // debouce 函数执行了,返回一个函数,该函数为事件的回调函数 // 事件真正的处理函数(handler),参数是回调函数传递过来的。 // 常见场景就是边输入查询关键字,边请求查询数据,好比百度的首页搜索 function ajax(event) { console.log("HTTP 异步请求:", event.target.value); // $.ajax() 请求数据 ... } function debounce(func, delay) { // 参数为传入的事件处理函数和间隔时间 var interval = delay || 1000; var timer = null; // 闭包保存的 timer 变量,会常驻内存 return function(args) { // 返回的匿名函数是事件的回调函数,在事件触发时执行,参数为 DOM 事件对象(event) var context = this; // 事件的回调函数中,this 指向事件的绑定的 DOM 元素对象(HTMLElement) console.log(timer); clearTimeout(timer); // 若是事件回调函数中存在定时器,则清空上次定时器,从新计时。若是间隔时间到后,处理函数天然就被执行了。 timer = setTimeout(function() { func.call(context, args); // 定时器时间到后,执行事件真正的处理函数 handler // 执行的事件处理函数(handler),须要把调用对象 this 和事件对象 传递过去,就像没被debounce处理过同样 }, interval) } } } </script>
复制代码
上面代码中个人注释已经可以说明整个去抖的过程,再来啰嗦几句话~dom
以上就是去抖函数的基本思想, 能够参考示意图
下面这张图是高设 3 里讲的节流函数,实际上是这一节所说的去抖函数,高设 3 将 timer 变量用传入的处理函数的属性代替了而已。
第二节的简单版去抖函数能知足大部分只须要触发一次事件处理的去抖场景:输入框输入关键字查询搜索结果。
可是有一个问题,假如我想输入框输入内容时,第一个字输完就请数据怎么作? 你能够理解为,你能够立刻开始说话,可是说完话后 5 分钟不能说话,若是 5 分钟内说话,则接下来再加 5 分钟不能说话。若是 5 分钟后没说话, 那么接下来,你又能够先说话,而后闭嘴 5 分钟~
因此,引出来了当即执行版的去抖函数。
取消功能实现
<div>
输入框: <input type="text" id="exampleInput">
</div>
<script> window.onload = function() { var inputEl = document.getElementById("exampleInput"); inputEl.oninput = debounce(ajax, 1000, true); // debouce 函数执行了,返回一个函数,该函数为事件的回调函数 // 事件真正的处理函数(handler),参数是回调函数传递过来的。 function ajax(event) { console.log("HTTP 异步请求:", event.target.value); } function debounce(func, delay, immediate) { var interval = delay || 1000; var timer = null; // 定时器的初始值为 null, 因此第一次触发事件会当即执行,整个过程当中 timer 充当了 flag 的做用,判断可否当即执行(第一次或者上一次当即执行后超过了间隔时间) return function(args) { var context = this; // 事件的回调函数中,this 指向事件的绑定的 DOM 元素对象(HTMLElement) console.log(timer); clearTimeout(timer); // 每次有新事件触发,都会清除以前的定时器,若是能够当即执行则执行,若是不能够当即执行则从新建立定时器。 if (immediate) { // 若是上一次的 timer 不为 null, 说明自上一次事件触发而且当即执行处理函数后,间隔时间还未结束。因此 timer 本应为数字 id,不为 null! callNow = !timer; timer = setTimeout(function() { timer = null; // 每次事件触发,并在定时器时间超事后, 把定时器变量设置 null, 从而能够判断出下一次是否可以当即执行。 }, interval); if (callNow) { func.call(context, args); } } else { timer = setTimeout(function() { func.call(context, args); // 定时器时间到后,执行事件真正的处理函数 handler }, interval) } } } } </script>
复制代码
上面代码的注释,能够解释整个流程,下面大体说一下:
看看效果:
假如去抖函数的间隔时间为 5 秒钟,我在这 5 秒钟内又想当即执行能够怎么作?因而咱们给回调函数加个取消函数属性。
函数也是一个对象,能够像其余通常对象那样添加方法:
<div>
输入框: <input type="text" id="exampleInput"><button id="cancelBtn">取消</button>
</div>
<script> window.onload = function() { var inputEl = document.getElementById("exampleInput"); var debouncedFunc = debounce(ajax, 5000, true); // 将事件处理函数通过去抖函数处理。 inputEl.oninput = debouncedFunc; // 绑定去抖后的事件回调函数 var cancelBtnEL = document.getElementById("cancelBtn"); cancelBtnEL.onclick = debouncedFunc.cancel; // 绑定回调函数的属性 cancel 方法,点击页面,重置去抖效果 function ajax(event) { console.log("HTTP 异步请求:", event.target.value); } function debounce(func, delay, immediate) { var interval = delay || 5000; var timer = null; var revokeFunc = function(args) { var context = this; clearTimeout(timer); if (immediate) { callNow = !timer; timer = setTimeout(function() { timer = null; }, interval); if (callNow) { func.call(context, args); } } else { timer = setTimeout(function() { func.call(context, args); }, interval) } } revokeFunc.cancel = function() { clearTimeout(timer); // 清空上一次事件触发的定时器 timer = null; // 重置 timer 为 null, 从而下一次事件触发就能当即执行。 } return revokeFunc; } } </script>
复制代码
看看效果:
去抖函数的意义在于合并屡次事件触发为一次事件处理,从而下降事件处理函数可能引起的大量重绘重排,http 请求,内存占用和页面卡顿。
另外,本文有关 this, call, apply,闭包的知识,能够翻看我以前分享的文章。
欢迎关注个人我的公众号“谢南波”,专一分享原创文章。