本文深入解析 JavaScript 中控制高频事件的三种核心策略:Throttle(节流)、Debounce(防抖)以及 Immediate 模式。通过对比帧率限制、状态变量与定时器重置机制,详细阐述如何在保证性能的前提下精确控制回调函数的执行频率与时机,并附带具体代码实现方案。
前言
我们将这些行为称为事件(events)及其对应的响应回调(callbacks)。连续发生的事件流构成事件流(event stream)。虽然我们无法手动控制事件发生的频率,但可以通过技术手段精确控制何时以及如何激活相应的响应逻辑。
Throttle(节流)原理与实现
Throttle(节流)
在现代浏览器中,60fps 的帧率通常被视为流畅性能的目标,这意味着我们仅有约 16.7ms 的时间预算来处理事件并执行必要的更新。由此可以推断,如果每秒发生 n 个事件,且回调执行需要 t 秒,为了确保运行流畅,必须满足特定的频率约束。
1 / n >= t
若 t 的单位为毫秒,则需满足公式:1000 / n >= t。
1000 / n >= t
如果你曾处理过 mousemove 事件,便会发现其触发频率每秒可能超过 60 次。一旦回调执行时间超过 16.7ms,页面渲染就会出现卡顿或混乱。
var then = 0;
function log() {
var now = Date.now();
if (1000 / (now - then) > 60) {
console.log('It\'s over 9000!!!');
}
then = now;
}
window.onmousemove = log;
实现方案
Throttle(节流)的核心在于限制响应激活的频率。我们可以设定每秒允许回调执行的次数,或者更直观地,设定在两次回调激活之间必须等待的时间间隔(delta)。
var delta = 1000;
var then = 0;
function log() {
console.log('foo');
}
function throttledLog() {
var now = Date.now();
if (now - then >= delta) {
log();
then = now;
}
};
window.onmousemove = throttledLog;
我们可以使用 fps(帧率)参数替换 delta 时间,从而推导出不同的代码逻辑。
var fps = 60;
...
function throttledLog() {
var now = Date.now();
if (1000 / (now - then) < = fps) {
log();
then = now;
}
};
window.onmousemove = throttledLog;
此外,还可以利用 setTimeout 实现相同效果。与直接检查时间差不同,这种方法通过检查状态变量的变化来控制流程。
首次触发时,可以安全地激活回调。执行完毕后,必须等待设定的 delta 时间过后,才能再次激活回调。
var delta = 1000;
var safe = true;
function log() {
console.log('foo');
}
function throttledLog() {
if (safe) {
log();
safe = false;
setTimeout(function() {
safe = true;
}, delta);
}
};
window.onmousemove = throttledLog;
Debounce(防抖)原理与实现
Debounce(防抖)
“去抖动”这一术语源自电子学领域,主要指手动开关输入信号传输至数字电路时的处理。在物理层面,按下按钮一次时,由于金属触点、弹簧或磨损等物理特性,数字电路可能会读取到多次按压信号。
去抖动的目的是将这些波动的信号合并,将其视为单一的一次有效输入。
应用场景示例
JavaScript 中已存在一个简单示例:keydown(按键按下)与 keyup(按键释放)的区别。假设你正在开发一个需要输入内容的功能,并希望每次敲击键盘仅获取一个字符。如果长按按键,keydown 事件会连续触发,而 keyup 事件仅在按键释放时触发一次。
window.onkeyup = function() {
console.log('onkeyup');
}
window.onkeydown = function() {
console.log('onkeydown');
}
这种行为差异有助于判断输入是否完成。在上述场景中,应使用 keyup 事件。从某种意义上说,keydown 是原始输入,而 keyup 则是经过“去抖动”处理的输入。
实现方案
当事件触发时,我们不立即激活回调,而是等待一段预设时间,并检查在此期间是否有相同事件再次触发。如果有,则重置定时器并继续等待;如果在等待期间未发生相同事件,则立即激活回调。
var delta = 1000;
var timeoutID = null;
function log() {
console.log('foo');
}
function debouncedLog() {
clearTimeout(timeoutID); // reset timer
timeoutID = setTimeout(function() {
// wait for some time
// and check if event happens again
log();
}, delta);
};
window.onkeydown = debouncedLog;
Immediate 模式解析
Immediate(立即执行)
Immediate 是 Debounce(防抖)的精确变体。与 Debounce 等待后续事件触发后再激活回调不同,Immediate 模式会在首次触发时立即激活回调,随后在设定的时间内忽略后续事件,直到时间结束。
实现方案
与 Throttle 类似,我们需要一个状态变量(如 safe)来判断是否应该激活回调。在 Debounce 中,由于 timeoutID 隐式管理了这部分逻辑,因此不需要额外的状态变量。
var delta = 1000;
var timeoutID = null;
var safe = true;
function log() {
console.log('foo');
}
function immediatedLog() {
if (safe) {
log();
safe = false;
}
clearTimeout(timeoutID);
timeoutID = setTimeout(function() {
safe = true;
}, delta);
};
window.onkeydown = immediatedLog;
总结
以上就是本文的全部内容。本文探讨了用作定时函数的最常见技术,希望这些内容能对您的学习或工作有所帮助。如有任何疑问,欢迎留言交流。