何为去抖函数?在学习Underscore去抖函数以前咱们须要先弄明白这个概念。不少人都会把去抖跟节流两个概念弄混,可是这两个概念实际上是很好理解的。javascript
去抖函数(Debounce Function),是一个能够限制指定函数触发频率的函数。咱们能够理解为连续调用同一个函数屡次,只获得执行该函数一次的结果;可是隔一段时间再次调用时,又能够从新得到新的结果,具体这段时间有多长取决于咱们的设置。这种函数的应用场景有哪些呢?java
好比咱们写一个DOM事件监听函数,git
window.onscroll = function(){
console.log('Got it!');
}
复制代码
如今当咱们滑动鼠标滚轮的时候,咱们就能够看到事件被触发了。可是咱们能够发如今咱们滚动鼠标滚轮的时候,咱们的控制台在不断的打印消息,由于window的scroll事件被咱们不断的触发了。github
在当前场景下,可能这是一个无伤大雅的行为,可是能够预见到,当咱们的事件监听函数(Event Handler)涉及到一些复杂的操做时(好比Ajax请求、DOM渲染、大量数据计算),会对计算机性能产生多大影响;在一些比较老旧的机型或者较低版本的浏览器(尤为IE)中,极可能会致使死机状况的出现。因此这个时候咱们就要想办法,在指定时间段内,只执行必定次数的事件处理函数。浏览器
说了一些概念和应用场景,可是仍是很拗口,到底什么是去抖函数?闭包
咱们能够经过以下实例来理解:app
假设有如下代码:异步
//本身实现的简单演示代码,未实现immediate功能,欢迎改进。
var debounce = function (callback, delay, immediate) {
var timeout, result;
return function () {
var callNow;
if (timeout)
clearTimeout(timeout);
callNow = !timeout && immediate;
if (callNow) {
result = callback.apply(this, Array.prototype.slice.call(arguments, 0));
timeout = {};
}
else {
timeout = setTimeout(() => {
callback.apply(this, Array.prototype.slice.call(arguments, 0));
}, delay);
}
};
};
var s = debounce(() => {
console.log('yes...');
}, 2000);
window.onscroll = s;
复制代码
debounce函数就是我本身实现的一个简单的去抖函数,咱们能够经过这段代码进行实验。函数
步骤以下:性能
经过以上步骤,咱们能够发现当咱们连续滚动鼠标时,控制台没有消息被打印出来,中止2s之内并再次滚动时,也没有消息输出;可是当咱们中止的时间超过2s时,咱们能够看到控制台有消息输出。
这就是去抖函数。在连续的触发中(不管时长),只能获得触发一次的效果。在指定时间长度内连续触发,最多只能获得一次触发的效果。
underscore源码以下(附代码注释):
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
//去抖函数,传入的函数在wait时间以后(或以前)执行,而且只会被执行一次。
//若是immediate传递为true,那么在函数被传递时就当即调用。
//实现原理:涉及到异步JavaScript,屡次调用_.debounce返回的函数,会一次性执行完,可是每次调用
//该函数又会清空上一次的TimeoutID,因此实际上只执行了最后一个setTimeout的内容。
_.debounce = function (func, wait, immediate) {
var timeout, result;
var later = function (context, args) {
timeout = null;
//若是没有传递args参数,那么func不执行。
if (args) result = func.apply(context, args);
};
//被返回的函数,该函数只会被调用一次。
var debounced = restArgs(function (args) {
//这行代码的做用是清除上一次的TimeoutID,
//使得若是有屡次调用该函数的场景时,只执行最后一次调用的延时。
if (timeout) clearTimeout(timeout);
if (immediate) {
////若是传递了immediate而且timeout为空,那么就当即调用func,不然不当即调用。
var callNow = !timeout;
//下面这行代码,later函数内部的func函数注定不会被执行,由于没有给later传递参数。
//它的做用是确保返回了一个timeout,而且保持到wait毫秒以后,才执行later,
//清空timeout。而清空timeout是在immediate为true时,callNow为true的条件。
//timeout = setTimeout(later, wait)的存在是既保证上升沿触发,
//又保证wait内最多触发一次的必要条件。
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
//若是没有传递immediate,那么就使用_.delay函数延时执行later。
timeout = _.delay(later, wait, this, args);
}
return result;
});
//该函数用于取消当前去抖效果。
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
复制代码
能够看到underscore使用了闭包的方法,定义了两个私有属性:timeout和result,以及两个私有方法later和debounced。最终会返回debounced做为处理以后的函数。timeout用于接受并存储setTimeout返回的TimeoutID,result用于执行用户传入的func函数的执行结果,later方法用于执行传入的func函数。
利用了JavaScript的异步执行机制,JavaScript会优先执行完全部的同步代码,而后去事件队列中执行全部的异步任务。
当咱们不断的触发debounced函数时,它会不断的clearTimeout(timeout),而后再从新设置新的timeout,因此实际上在咱们的同步代码执行完以前,每次调用debounced函数都会重置timeout。因此异步事件队列中的异步任务会不断刷新,直到最后一个debounced函数执行完。只有最后一个debounced函数设置的later异步任务会在同步代码执行以后被执行。
因此当咱们在以前实验中不断的滚动鼠标时,其实是在不断的调用debounced函数,不断的清除timeout对应的异步任务,而后又设置新的timeout异步任务。当咱们中止的时间不超过2s时,timeout对应的异步任务尚未被触发,因此再次滚动鼠标触发debounced函数还能够清除timeout任务而后设置新的timeout任务。一旦中止的时间超过2s,最终的timeout对应的异步代码就会被执行。