防抖(debounce)
防抖的作用是将多个连续的debounced
调用合并为一次func
调用。作用见参考资料1。
- 两次
debounced
调用的间隔小于waitTime
,则视为连续的调用。 - 如果距离上次
debounced
调用已经过去了waitTime
的时间,则说明该轮连续调用已经结束(进入稳定状态)。这个时间点也被称为trailing edge。 - 在trailing edge以后的第一次
debounced
调用是下一轮连续调用的开始。当然,第一次debounced
调用也是一轮连续调用的开始。这个时间点也被称为leading edge。 -
immediate
参数可以控制是否在leading edge执行一次func
调用。callAfterStable
参数控制是否在trailing edge执行一次func
调用。因此,func
调用可以放在连续调用开始时,也可以放在结束时,也可以都放。一般设置immediate = false,callAfterStable = true
,将func
调用放在连续调用结束时。 - 假设
debounced
的调用一直持续不断,且相邻间隔都小于waitTime
,则意味着连续调用一直没有结束,放在trailing edge的func
调用一直不会执行。
function debounce(
func,
waitTime = 1000,
immediate = false,
callAfterStable = true
) {
if (!immediate && !callAfterStable)
throw new Error("immediate 和 callAfterStable 不能同时为false"); // 否则func.apply永远不会调用
let timeout = null;
const debounced = function(...args) {
// timeout的值决定当前是否处于稳定状态(已经经过waitTime没有被调用了)
// 如果已经存在一个定时器,说明现在是处于一轮连续调用当中(非稳定状态),需要重新计时
if (timeout) clearTimeout(timeout);
// 否则,此时是leading edge。如果配置了immediate,此时要触发func
else if (immediate) func.apply(this, args);
// trailing edge将在waitTime时间以后到来,进入稳定状态(前提是这段时间内没有被调用)
timeout = setTimeout(() => {
// 这个回调被执行时,说明已经经过waitTime没有被调用了,进入稳定状态
timeout = null;
// 此时是trailing edge。如果配置了callAfterStable,要触发func
if (callAfterStable) func.apply(this, args);
}, waitTime);
};
// 使用者可以调用这个函数,强行进入稳定状态
debounced.forceStabilize = function() {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
return debounced;
}
节流(throttle)
节流的作用是限制func
调用的频率(最多每waitTime
调用一次)。作用见参考资料2。
防抖与节流之间的重要区别是,防抖是基于上次debounced
调用来计算waitTime
的;而节流是基于上次func
调用来计算waitTime
的,只要距离上次func
调用超过了waitTime
,就可以进行下次func
调用。
实现2修改自参考资料2。个人认为实现1更好理解。
实现1
// immediate传入true,将在leading edge就第一次调用func
// 否则,将在 leading edge+waitTime 的时候才第一次调用func
function throttle(func, waitTime = 1000, immediate = true) {
let timeout = null,
// called表示自从上次func调用以后,是否是否有调用过throttled
called,
// 存储上一次调用throttled时提供的args和this,用来在timeExpired时调用func
lastArgs,
lastThis;
function timeExpired() {
if (called) {
func.apply(lastThis, lastArgs);
called = false;
timeout = setTimeout(timeExpired, waitTime);
} else {
// trailing edge
// trailing edge不调用func了,
// 因为在waitTime之前调用过了func,且自从那以后,throttled就没有被调用过。
timeout = null;
// 释放内存
lastArgs = lastThis = null;
}
}
function throttled(...args) {
lastArgs = args;
lastThis = this;
if (!timeout) {
// leading edge
if (immediate) {
func.apply(lastThis, lastArgs);
called = false;
} else {
// !immediate时,leading edge下一次的timeExpired必须调用func
// 否则,如果在(leading edge, leading edge + waitTime]这段时间内没有调用过throttled,func一次也不会执行
called = true;
}
timeout = setTimeout(timeExpired, waitTime);
} else {
called = true;
}
}
throttled.cancle = function() {
if (timeout) {
clearTimeout(timeout);
timeout = null;
lastArgs = lastThis = null;
}
};
return throttled;
}
实现2
function throttle(
func,
waitTime = 1000,
immediate = true,
callAfterStable = true
) {
if (!immediate && !callAfterStable)
throw new Error("immediate 和 callAfterStable 不能同时为false"); // 下面会指出原因
let timeout = null,
// 上一次调用func的时间
previous = 0;
const throttled = function(...args) {
const now = Date.now();
// immediate==false时,previous==0有特殊的含义:当前处于稳定状态,本次调用throttled不立即触发func
// 阻止立即触发func的方式:previous = now,相当于0秒前刚刚调用过了func
// 因此稳定状态下的第一次throttled调用会进入elseif,将func推迟调用
if (!previous && !immediate) previous = now;
const remain = waitTime - (now - previous);
// immediate 和 callAfterStable 不能同时为false,否则if和elseif语句块都永远不会调用
if (remain < 0 || remain > waitTime) {
// 距离上一次调用func至少经过了waitTime,本次throttled立即触发func
if (timeout) {
// 有可能有timer回调仍阻塞在时间队列中(虽然肯定已经超时),销毁它
clearTimeout(timeout);
timeout = null;
}
func.apply(this, args);
previous = now;
} else if (!timeout && callAfterStable) {
// throttled调用时,距离上一次调用func还没有过去waitTime,
// 不立即触发func,而是安排到previous+waitTime时刻
// 判断!timeout是为了防止安排多个func在previous+waitTime时刻调用
timeout = setTimeout(() => {
func.apply(this, args);
// immediate==false时,previous=0表示进入稳定状态,设置它是为了阻止下一次的immediate调用
previous = immediate ? Date.now() : 0;
timeout = null;
}, remain);
}
};
throttled.forceStabilize = function() {
previous = 0;
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
return throttled;
}
测试代码
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
<title>test</title>
<style>
#container {
width: 100%;
height: 200px;
line-height: 200px;
text-align: center;
color: #fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="lib.js"></script>
<script>
var count = 1;
var container = document.getElementById("container");
function getUserAction() {
container.innerHTML = count++;
}
// container.onmousemove = debounce(getUserAction);
container.onmousemove = throttle(getUserAction);
</script>
</body>
</html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。