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));
相关推荐
编程猪猪侠28 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞32 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界1 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架