文章会用到的主要知识有闭包与类的封装,可阅读前期文章阅读阮一峰教程:一文讲清JavaScript 闭包
防抖(Debounce)
防抖的核心思想是:在事件被频繁触发时,只在最后一次触发后的一段时间后执行一次函数。如果在这段时间内又被触发,则重新计时。
为什么要防呢?因为不防的话,触发的频率太高,会直接造成服务器的"宕机"。
我们会经常遇见防抖,比如我们使用百度,谷歌或掘金的搜索引擎搜索:防抖,等待一会儿就会出现相关的内容。这种应用场景最初由Google提出,所以叫Google Suggest,一般来说叫做搜索框输入自动联想。
还有许许多多的应用场景,如窗口大小调整(resize),表单验证等等。
代码简单实现
function debounce(fn, delay) {
return function (args) {
clearTimeout(fn.id);
fn.id = setTimeout(() => {
fn(args);
}, delay);
};
}
分析:debounce 返回一个新函数,每次调用时都会清除上一次的定时器(clearTimeout(fn.id)),并重新设置一个新的定时器(setTimeout)。只有在最后一次触发后的 delay 毫秒后,fn 才会被执行。通过 fn.id 这个属性,利用闭包特性为每个函数单独保存定时器 id,避免全局变量污染。
防抖时序图
sequenceDiagram
participant 用户
participant 防抖函数
用户->>防抖函数: 连续输入
防抖函数-->>防抖函数: 清除上一次定时器
防抖函数-->>防抖函数: 重新设置定时器
Note right of 防抖函数: 只有最后一次输入后 delay 毫秒才会执行
节流(Throttle)
节流的核心思想是:在一段时间内,无论事件被触发多少次,函数只会按照固定频率执行。
如下图:
就是一个应用场景,相关的应用场景还有页面滚动(scroll)事件,拖拽(drag)事件和处理高频点击事件等等。
代码实现与讲解
function throttle(fn, delay) {
let last, deferTimer;
return function () {
let that = this;
let now = +new Date(); // 类型转换
let args = arguments;
if (last && now < last + delay) {
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(that, args);
}, delay);
} else {
last = now;
fn.apply(that, args);
}
};
}
代码分析:throttle 返回一个新函数,每次调用时会判断距离上一次执行是否超过 delay 毫秒。如果未超过,则通过 setTimeout 延迟执行,并清除上一次的延迟(保证只执行最后一次)。如果超过,则立即执行,并更新时间戳。通过闭包保存 last 和 deferTimer,保证每个节流函数的独立性。
keyup节流示例:
let throttleAjax = throttle(ajax, 1000);
inputC.addEventListener("keyup", function (e) {
throttleAjax(e.target.value);
});
如上例,输入框的 keyup 事件会被节流处理,避免高频率触发 ajax 请求。
节流时序图
sequenceDiagram
participant 用户
participant 节流函数
用户->>节流函数: 高频触发
节流函数-->>节流函数: 判断时间间隔
alt 超过间隔
节流函数->>ajax: 立即执行
else 未超过间隔
节流函数-->>节流函数: 延迟执行
end
总结
防抖适合只关心最后一次操作的场景,节流适合需要定期响应的场景。两者都有普遍的应用场景。
闭包是实现防抖与节流的关键技术,能有效管理私有变量和状态。