防抖指的是在一定时间内,函数只能执行一次,如果在这个时间内再次触发,则重新计算时间,直到时间到了,才执行函数,这样就可以避免频繁触发函数,造成性能浪费。
今天带来的是underscore
中的防抖函数,loadash
中也有,但是loadash
中的防抖函数阅读起来会有比较大的心智负担。
防抖的实现
underscore
的debounce
的实现比较简单,核心代码如下:
import restArguments from './restArguments.js'; import now from './now.js'; // When a sequence of calls of the returned function ends, the argument // function is triggered. The end of a sequence is defined by the `wait` // parameter. If `immediate` is passed, the argument function will be // triggered at the beginning of the sequence instead of at the end. export default function debounce(func, wait, immediate) { var timeout, previous, args, result, context; var later = function() { var passed = now() - previous; if (wait > passed) { timeout = setTimeout(later, wait - passed); } else { timeout = null; if (!immediate) result = func.apply(context, args); // This check is needed because `func` can recursively invoke `debounced`. if (!timeout) args = context = null; } }; var debounced = restArguments(function(_args) { context = this; args = _args; previous = now(); if (!timeout) { timeout = setTimeout(later, wait); if (immediate) result = func.apply(context, args); } return result; }); debounced.cancel = function() { clearTimeout(timeout); timeout = args = context = null; }; return debounced; }
上面的两个引用就不看了,我简单的介绍一下他们的作用:
restArguments
:看名字大概就知道,是用来处理剩余参数的,因为可能有些函数的参数是不确定的,所以需要用这个来处理一下。now
:获取当前时间,用来计算时间差。
源码解析
当我们在使用debounce
的时候,会传入三个参数,func
是我们需要防抖的函数,wait
是防抖的时间,immediate
是是否立即执行。
var debounced = debounce(function() { console.log('debounce'); }, 500, true);
可以看到上面的执行截图,我们执行了很多次,但是最终只执行了两次;
第一次执行是立即执行;
第二次执行是在等了500ms
之后再次调用,然后立即执行的。
返回一个函数
防抖函数返回的就是一个函数,我们将去执行返回的函数,这个就是通过闭包实现的;
underscore
中的debounce
函数使用了restArguments
来处理剩余参数,这个函数也是返回一个函数,我们分析的时候可以忽略这个函数:
function debounce(func, wait, immediate) { var timeout, previous, args, result, context; var debounced = function() { context = this; args = [].slice.call(arguments); previous = Date.now(); if (!timeout) { timeout = setTimeout(later, wait); if (immediate) result = func.apply(context, args); } return result; } return debounced; }
通过简化代码,我们现在可以很直观的看到返回的函数内容;
这里使用[].slice.call(arguments)
来处理参数的问题,当前时间使用Date.now()
来获取。
定时器
防抖的核心其实并不是返回的函数,而是定时器,我们来看看定时器的实现;
在上面的代码中可以看到,当我们执行返回的函数的时候,会先判断是否有定时器,如果没有定时器是不会执行对应的函数的;
来看看定时器的实现:
var later = function () { var passed = Date.now() - previous; if (wait > passed) { timeout = setTimeout(later, wait - passed); } else { timeout = null; if (!immediate) result = func.apply(context, args); // This check is needed because `func` can recursively invoke `debounced`. if (!timeout) args = context = null; } };
previous
是上一次执行的时间,wait
是防抖的时间,passed
是当前时间和上一次执行的时间差;
如果wait
大于passed
,说明还没有到防抖的时间,所以会重新设置定时器,这样就可以保证在防抖的时间内,只执行一次;
如果wait
小于passed
,说明已经到了防抖的时间,这时候就会执行对应的函数,如果是立即执行的话,就说明执行过了, 就不需要再执行了,后面就是一些释放内存的操作。
cancel
最后还有一个cancel
方法,这个方法是用来取消防抖的,给我们提供了一个手动取消的方法;
debounced.cancel = function () { clearTimeout(timeout); timeout = args = context = null; };
cancel
方法是直接挂在返回的函数上的,所以我们可以在外部手动调用这个方法来取消防抖。
总结
防抖函数的实现其实并不复杂,核心就是定时器,我们可以通过定时器来控制函数的执行,从而达到防抖的效果。
同时,underscore
中的防抖函数还提供了一个cancel
方法,这样我们可以在外部手动取消防抖,例如组件销毁的时候,这样就可以进一步的优化性能,虽然有点微乎其微,但是有这样的一个设计可以说是考虑的非常周到了。
对比于lodash
的防抖函数,underscore
的防抖函数更加的简洁,这也是underscore
的风格,简洁,高效。