JavaScript 防抖和节流

概念

防抖(Debounce)是指在事件触发后,只有在经过设定的时间间隔内没有再次触发,事件处理器才会执行。防抖限制事件的触发时间间隔,避免事件被频繁触发。这适用于在搜索框输入建议、调整窗口大小、表单验证、表单提交等场景。

节流(Throttle)则是指在一定时间间隔内,不论事件触发多少次,事件处理器只执行一次。节流控制执行时间间隔,降低事件的执行频率。这适用于页面滚动加载、鼠标移动、游戏角色控制、实时数据图表更新等场景。

注意:事件触发≠函数执行。触发在执行前。

比如,我反馈了一个Bug,但是该Bug不一定在被修复。
疑问:防抖和节流都可用于控制事件处理函数的执行频率,那它们能否互相替代?

防抖是为了确保多次触发、一次执行,只关注最终状态;节流是为了确保函数的周期性执行,更关注过程中的均匀响应。

故防抖、节流不能完全互相替代,也就是说它两的应用场景是有所侧重的。

比如,对于搜索框输入建议这个场景,理应使用防抖而非节流,理由:我们监听的是输入事件,且应该在用户输入完想要输入的内容后再给出建议,若使用节流,就可能在用户还没输入完成时给出了无效建议。

实现

防抖实现

(1)防抖开始时不调用,结束时才调用。

javascript 复制代码
function debounce(func, delay = 0){
    let timer = null;

    return function(){
        clearTimeout(timer);
        timer = setTimeout(()=>{
            func.apply(this, arguments);
        }, delay);
    }
}

(2)防抖开始时立即调用,结束时也调用。

ini 复制代码
// func 是待防抖函数
// delay 是最短执行时间间隔,单位 ms
// options 是额外配置,包括首次调用立即执行
function debounce(func, delay, options = {}) {
    let timer; // 计时器 ID
    const { leading = false } = options; // 默认首次调用不立即执行

    const debounced = function (...args) {
        // 保留执行时上下文
        const context = this;

        // 首次调用执行
        if (leading && !timer) func.apply(context, args);
        
        // 控制触发时间间隔的关键点
        clearTimeout(timer);
        timer = setTimeout(()=>{
            // 避免重复执行,首次调用执行了尾部延迟结束就不再执行
            if (!leading) func.apply(context, args);
            
            timer = null;
        }, delay);
    };

	// 取消防抖的功能函数
    debounced.cancel = () => {
        clearTimeout(timer);
        timer = null;
    };

    return debounced;
}

为什么要在更新 timernull前执行 clearTimeout

更新 timernull不会使得timer关联的计时器失效。

节流实现

(1)时间戳版本:强调在节流开始时就会调用函数,节流结束时不调用。

ini 复制代码
function throttle(func, delay = 0){
    let lastExecTime = 0;
    
    return function(){
        let context = this;
        let args = arguments;
        let remaining = delay - (Date.now() - lastExecTime);

        if(remaining < 0){
            func.apply(context, args);
            lastExecTime = Date.now();
        }
    }
}

(2)定时器版本:关注点在节流结束时再调用函数,而开始时不会调用函数。

ini 复制代码
function throttle(func, delay = 0){
    let timer = null;
    
    return function(){
        let context = this;
        let args = arguments;

        if(!timer){
            timer = setTimeout(()=>{
                timer = null;
                func.apply(context, args);
            }, delay);
        }
    }
}

(3)合并版本:结合使用时间戳和定时器,节流开始和结束时都会执行。

ini 复制代码
// option 配置项:
//  - leading:在节流开始时执行
//  - trailing:在节流结束后执行
function throttle(func, delay = 0, option = {}){
    let timer = null;
    let lastExecTime = 0;
    const { leading = true, trailing = true } = option;
    
    const throttled = function(...args) {
        const context = this;
        const remaining = delay - (Date.now() - lastExecTime);

        if(leading && remaining < 0) {
            lastExecTime = Date.now();
            if(timer){
                clearTimeout(timer);
                timer = null;
            }
            func.apply(context, args);
        }
        else if(trailing && remaining >= 0 && !timer) {
            timer = setTimeout(()=>{
                lastExecTime = Date.now();
                timer = null;
                func.apply(context, args);
            }, remaining);
        }
    }

    return throttled;
}

应用示例:

🌺对鼠标移动事件添加节流:

javascript 复制代码
window.addEventListener('mousemove', throttle(function(){
    console.log('鼠标移动');
}, 1000));
相关推荐
往事随风灬5 分钟前
我被 Volta 的“智能”坑了一下午:pnpm 为何无视项目 Node 版本?
前端·vue.js
xiaofeichaichai9 分钟前
Tree Shaking
前端·javascript
lichenyang4539 分钟前
给 ArkTS 应用做一个内置的「Network 面板」:实时看清 SSE 每一帧和最后那张卡片
前端
倾颜12 分钟前
从手写 Runner 到 LangGraph:受控 Agent 接入 LangGraph
前端·后端·langchain
UXbot20 分钟前
AI网页开发工具能替代工具吗?5大平台对比
前端·人工智能·低代码·ui·原型模式·web app
wuhen_n22 分钟前
从零到一!前端搭建本地轻量化 RAG 问答系统
前端·langchain·ai编程
落日漫游39 分钟前
代码报错难排查?借助Gemini快速修复
前端
niconicoC39 分钟前
让 Three.js 场景更真实:我用高斯泼溅和 SparkJS 做了一个可交互的 3D Demo
前端·webgl
Darling噜啦啦42 分钟前
JavaScript 数组深度解析:从纯函数到二维数组陷阱,一文吃透前端数据结构核心
前端·javascript·数据结构
万少43 分钟前
一封邮件,让我重新打开了搁置半年的鸿蒙应用
前端·javascript·后端