深入 JS 之分析防抖与节流
防抖
防抖是什么?
对于一定时间段的连续的函数调用,只让其执行一次。
常用于 DOM 事件的监听回调中:核心是维护一个定时器,事件被触发 n 秒后再执行回调,如果 n 秒内又被触发,则重新计时。
什么时候应用防抖
前端开发中会遇到一些频繁的事件触发,如:
- window 的 resize、scroll
- mousedown、mousemove
- keyup、keydown
这些事件触发后都会执行一个回调函数,如果短时间内有很多次事件触发,则浏览器需要短时间内执行很多次回调,如果次数过于频繁,浏览器会反应不过来,就会出现 卡顿 现象,此时就需要防抖/节流。
举个例子:
// 模拟一段ajax请求 function ajax(context) { console.log('ajax request' + context); } let inputa = document.getElementById('unDebounce'); inputa.addEventListener('keyup', function(e) { ajax(e.target.value); })
此处也频繁执行了 keyup 的回调,因为这个例子很简单,所以浏览器完全反应的过来,但例子复杂的话,浏览器就会有卡顿了。此时我们可以通过 防抖 、 节流 来解决。
跟着 underscore 的防抖代码学习
核心是维护一个定时器,事件被触发n秒后再执行回调,如果n秒内又被触发,则重新计时。
// 模拟一段ajax请求 function ajax(context) { console.log('ajax request' + context.target.value); } function debounce(fn, wait) { var timeout = null; return function() { var context = this; // this指向DOM元素 var args = arguments; // JavaScript 在事件处理函数中会提供事件对象 event 作为参数。 clearTimeout(timeout); timeout = setTimeout(function() { fn.apply(context, args) }, wait) } } let inputb = document.getElementById('Debounce'); inputb.addEventListener('keyup', debounce(ajax, 1000))
可以看到,我们加入了防抖以后,当你在频繁的输入(事件频繁被触发)时,并不会发送请求,只有当你在指定间隔内没有输入时,才会执行函数。如果停止输入但是在指定间隔内又输入,会重新触发计时。
完善防抖函数 debounce
立即执行
上面的 debounce 函数已经可以满足很多场景了,但如果有这样一个需求:
我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
我们可以加个 immediate 参数判断是否是立刻执行,如下:
function debounce(fn, wait, immediate) { // immediate为立即执行一次的开关 var timeout = null; return function() { // 一开始立即执行一次fn,若在wait时间内再次触发事件执行回调,callNow 会为 false,不会调用到fn。若在wait时间后再触发事件则会再次立即执行。 var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function() { timeout = null; }, wait); if (callNow) fn.apply(context, args); } else { timeout = setTimeout(function() { fn.apply(context, args); }, wait) } } }
返回值
getUserAction 函数可能是有返回值的,所以我们也要返回函数的执行结果,但是当 immediate 为 false 的时候,因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以我们只在 immediate 为 true 的时候返回函数的执行结果。
function debounce(fn, wait, immediate) { var timeout = null; var result; return function() { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function() { timeout = null; }, wait); if (callNow) result = fn.apply(context, args); } else { timeout = setTimeout(function() { fn.apply(context, args); }, wait) } return result; } }
手动取消防抖
希望能取消 debounce 函数,比如说 debounce 的时间间隔是 10 秒钟,immediate 为 true,这样的话,我只有等 10 秒后才能重新触发事件,现在我希望有一个按钮,点击后,取消防抖,这样我再去触发,就可以又立刻执行了。
其实很简单,我们在函数上挂个 cancel 方法就行了:
function debounce(func, wait, immediate) { var timeout = null, result; var debounced = function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { // 如果已经执行过,不再执行 var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) result = func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } return result; }; debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; }
比如,需要用一个按钮,点击一下就可以取消,就可以这样使用:
var xxx = debounce(fn, 10000, true); inputb.addEventListener('keyup', xxx)); document.getElementById("button").addEventListener('click', function(){ xxx.cancel(); })
至此我们就实现了一个 underscore 中的 debounce 函数。
节流
节流是什么?
让一个函数不要执行得太频繁,减少一些过快的调用来节流
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
也常用于在DOM事件的监听回调中:如果你持续触发事件,每隔一段时间,只执行一次事件。
什么时候应用节流
函数节流有哪些应用场景?哪些时候我们需要间隔一定时间触发回调来控制函数调用频率?
- DOM 元素的拖拽功能实现(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 搜索联想(keyup)
- 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次。
按上面的例子:
function throttle(fun, delay) { let last, deferTimer return function (args) { let that = this let _args = arguments let now = +new Date() if (last && now < last + delay) { // 在设置的间隔时间内执行。这段代码保证在间隔时间内停止触发时,delay秒后还会执行一次。 clearTimeout(deferTimer) deferTimer = setTimeout(function () { last = now fun.apply(that, _args) }, delay) }else { // 一开始立即执行 last = now fun.apply(that,_args) } } }
小结
函数节流和函数去抖的核心其实就是限制某一个方法被频繁触发,而一个方法之所以会被频繁触发,大多数情况下是因为 DOM 事件的监听回调,而这也是函数节流以及去抖多数情况下的应用场景。
按照DOM事件的监听回调来说两者区别的话:防抖是只有等事件停止触发后 n 秒才执行函数,节流是持续触发的时候,每 n 秒执行一次函数。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 深入 JS 之数组去重
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论