FED实验室 - 专注WEB端开发和用户体验

谈谈Javascript中的throttle and debounce

点滴Javascript 煦涵 3479℃ 0评论

一、应用场景

在日常开发中,我们会经常遇到以下连续事件、频率控制及其造成的性能优化等问题:

1、鼠标事件:mousemove(拖曳)/mouseover(划过)/mouseWheel(滚屏)
2、键盘事件:keypress(基于ajax的用户名唯一性校验)/keyup(文本输入检验、自动完成)/keydown(游戏中的射击)
3、window的resize/scroll事件(DOM元素动态定位)

如果事件回调中包含很多处理程序、ajax请求和DOM操作,频繁的触发将是非常耗时和耗性能的,有时还会导致浏览器崩溃的问题。

如何解决上述问题,有两种方法:throttle(节流)和debounce(去抖)
throttle:连续的时间间隔(每隔一定时间间隔执行callback)。
debounce:空闲的时间间隔(callback执行完,过一定空闲时间间隔再执行callback)。

二、实现原理(resize事件为例)

有各需求缩放浏览器窗口时,实现页面宽高随窗口自适应。我们可能会像下面这样做:

var $win = $(window),
	setSize = function() {
		var winW = $win.width(),
			winH = $win.height();

		$("#wrapper").css({
			width: winW,
			height: winH
		});
	};

setSize();

//绑定事件
$win.on("resize", function(event){
	setTimeout(setSize, 1000);
});

但是我们这里有个问题,每次执行完定时器没有被清除,同时增加延迟参数,下面我们来封装一个函数:

var $win = $(window),
	throttle = function(fn, delay) {
		var timer = null;

		return function() {
			var context = this,
				args    = arguments;

			if(timer) {
				clearTimeout(timer);
			}

			timer = setTimeout(function() {
				fn.apply(context, args);
			}, delay);
		}
	},
	setSize = function() {
		var winW = $win.width(),
			winH = $win.height();

		$("#wrapper").css({
			width: winW,
			height: winH
		});
	};

setSize();

//绑定事件
$win.on("resize", throttle(function(event) {
	setSize();
}, 1000));

封装的throttle函数带有两个参数,一个fn执行函数,一个延迟时间间隔。上例中每隔1秒执行一次setSize()。

三、常见库的封装

现在比较流行的库实现的throttle和debounce的有:
Underscore.js by Jeremy Ashkenas
Lodash.js by John-David Dalton
jQuery Plugin by Ben Alman

以上三种实现方式中:
1、jQuery Plugin中的实现,详细描述,请参考文章jQuery throttle / debounce: Sometimes, less is more!
2、Underscore.js和Lodash.js实现方式不同,但是函数参数基本相同;
3、关于debounce,分析Underscore.js中的源码:

/**
 * [debounce description]
 * @param  {[type]} func      [回调函数]
 * @param  {[type]} wait      [等待时长]
 * @param  {[type]} immediate [是否立即执行]
 * @return {[type]}           [description]
 */
_.debounce = function(func, wait, immediate) {
	var timeout, args, context, timestamp, result;

	var later = function() {
		var last = _.now() - timestamp;

		//小于wait时间,继续延迟wait-last执行later,知道last >= wait才执行func
		if (last < wait && last > 0) {
			timeout = setTimeout(later, wait - last);
		} else {
			timeout = null;
			if (!immediate) {
				result = func.apply(context, args);

				if (!timeout) context = args = null;
			}
		}
	};

	return function() {
		context = this;
		args = arguments;
		timestamp = _.now();
		//是否立即执行
		var callNow = immediate && !timeout;

		if (!timeout) timeout = setTimeout(later, wait);

		if (callNow) {
			result = func.apply(context, args);
			context = args = null;
		}

		return result;
	};
};

综合以上应用情景中的情景1、3肯定是用throttle,情景2 throttle和debounce可以按需使用。

四、参考链接

Debounce and Throttle: a visual explanation

Throttling function calls

Underscore.js throttle vs debounce example - JSFiddle

jquery.ba-throttle-debounce.js

Simple Throttle Function

以上就是本文的描述,感谢您的阅读,文中不妥之处还望批评指正。

下面是「FED实验室」的微信公众号二维码,欢迎扫描关注:

FED实验室

行文不易,如有帮助,欢迎打赏!

赞赏支持or喜欢 (2)or分享 (0)
捐赠共勉
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址