防抖和节流 - panchaow/blog GitHub Wiki
假设我们要实现一个用户注册的功能,其中有一项用户名需要保证唯一。因此,我们需要在用户输入用户名之后,发起一次请求,确认用户名是否已经被占用。
于是我们想到只要监听<input>
的input事件,在这个事件触发的时候,发起请求。代码如下:
<input id="username" name="username" type="text">
<script>
const username = document.getElementById("username");
username.addEventListener("input", function (e) {
const value = e.target.value;
setTimeout(() => {
console.log(value);
}, 1000);
})
</script>
但是这样的实现存在着一个问题,事实上,我们只关心用户最后输入的值是否被占用,而不关心输入过程中的中间状态。比如用户连续输入了"orange",我们没必要在用户只输入了"ora"的时候就发起一次请求。那么该如何解决这个问题呢?我们只需要在用户输入用户名,并在一段时间内(比如100ms)没有继续输入时发起一次请求。利用防抖(debounce)高阶函数,我们就可以实现这种效果。
debounce
的实现:
function debounce(fn, wait = 0) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, wait);
}
}
使用防抖后的代码:
<input id="username" name="username" type="text">
<script>
const username = document.getElementById("username");
username.addEventListener("input", debounce(function (e) {
const value = e.target.value;
setTimeout(() => {
console.log(value);
}, 1000);
}, 100));
</script>
这样,就只会在用户输入后,并且在100ms内没有继续输入的情况下,发起一次请求。
假设我们需要实现用户移动鼠标时,根据鼠标的坐标进行某些计算的功能。代码如下:
document.addEventListener("mousemove", function(e) {
const { clientX, clientY } = e;
console.log(clientX, clientY);
});
这段代码存在的问题是如果要进行的计算比较昂贵,那么如此频繁的计算将可能会造成页面卡顿,影响用户体验。假如我们能够限制昂贵的计算在一段时间内(比如100ms)只执行一次,那么问题就可以被解决了。throttle
的实现如下:
function throttle(fn, wait) {
let inThrottle, lastTimer;
return function(...args) {
if (!lastTime) {
// 第一次调用
fn.apply(this, args);
lastTime = Date.now();
} else {
clearTimeout(lastTimer);
lastTimer = setTimeout(() => {
fn.apply(this, args);
lastTime = Date.now();
}, Math.max(wait - (Date.now() - lastTime), 0)); // 如果过了时间,那么立刻执行(但是还是异步的)
}
};
}
使用tottle
的例子:
document.addEventListener("mousemove", throttle(function(e) {
const { clientX, clientY } = e;
console.log(clientX, clientY);
}, 100));
这样的效果是,第一次调用函数时会立刻执行,之后的100ms内的除最后一次外的调用会被忽略,而最后一次调用会被延迟到距上一次执行100ms之后执行,然后重新开始计时。如果一次调用发生在距离上一次执行100ms后,那么立刻执行一次,并重新开始计时。