理解Underscore中的去抖函数

何为去抖函数?在学习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函数就是我本身实现的一个简单的去抖函数,咱们能够经过这段代码进行实验。函数

步骤以下:性能

  • 复制以上代码,打开浏览器,打开控制台(F12),而后粘贴代码并回车执行。
  • 接二连三的滚动鼠标,查看控制台有无输出。
  • 中止滚动鼠标,2s以内再次滚动鼠标,查看是否有输出。
  • 连续滚动以后中止2s以上,查看是否有输出。

经过以上步骤,咱们能够发现当咱们连续滚动鼠标时,控制台没有消息被打印出来,中止2s之内并再次滚动时,也没有消息输出;可是当咱们中止的时间超过2s时,咱们能够看到控制台有消息输出。

这就是去抖函数。在连续的触发中(不管时长),只能获得触发一次的效果。在指定时间长度内连续触发,最多只能获得一次触发的效果。

underscore的实现

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对应的异步代码就会被执行。

总结

  • 去抖是限制函数执行频率的一种方法。
  • 去抖后的函数在指定时间内最多被触发一次,连续触发去抖后的函数只能获得一次的触发效果。
  • underscore去抖的实现依赖于JavaScript的异步执行机制,优先执行同步代码,而后执行事件队列中的异步代码。

参考

相关文章
相关标签/搜索