什么是防抖和节流?二者有什么区别?如何实现?应用场景?

什么是防抖和节流

本质上是优化高频率执行代码的一种手段。

防抖和节流的区别

防抖(debounce) : n 秒后再执行一次函数,如果在 n 秒内重复触发,则重新计时。

节流(throttle): n秒内函数只执行一次,若在n秒内重复触发,只有一次生效

代码实现

防抖

javascript 复制代码
function debounce(func, delay) {
    let timeout;
    return function () {
        // 保存this指向
        let context = this;
        // 拿到event对象
        let args = arguments; 
        if(timeout) clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, delay);
    }
}

// 若想第一次就执行函数
function debounce(func, delay, immediate) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;
        if (timeout) clearTimeout(timeout); // timeout 不为null
        if (immediate) {
            let callNow = !timeout; // 第一次立即执行,后面只有事件执行后才会触发
            timeout = setTimeout(function () {
                timeout = null;
            }, delay)
            if (callNow) {
                func.apply(context, args)
             }
        } else {
            timeout = setTimeout(function () {
                func.apply(context, args)
            }, delay);
        }
    }
}

节流

  • 使用时间戳实现 (事件会立即执行,停止触发后没有办法再次执行 )
  • 使用定时器实现 (delay 毫秒后第一次执行,第二次事件停止触发后依然会再一次执行)
  • 使用时间戳加定时器实现更加准确的节流

下例1使用时间戳实现节流时,涉及闭包的知识点

  • 第一次点击oldtime初始化为调用throttled1时的时间,执行fn并更新oldtime
  • 后续点击 :闭包中的oldtime是上一次更新的值,不会重新初始化。

在JavaScript中,闭包 会保留其外部函数的作用域变量。当调用throttled1时,oldtime的初始化(let oldtime = Date.now())只会执行一次,因为throttled1函数本身通常只被调用一次(生成节流后的函数)。返回的闭包函数(事件处理函数)会持有这个oldtime的引用,并在多次触发时复用这个变量。因此,即使多次触发事件(如点击),oldtime的初始化代码不会重复执行。

  1. 闭包的作用域保留
    throttled1执行时,会创建一个作用域,其中oldtime被初始化。返回的闭包函数会捕获这个作用域,使得oldtime在闭包的生命周期内一直存在,而不是每次触发事件都重新初始化。
  2. 正确的节流逻辑
    节流的核心是跟踪上一次执行的时间(oldtime)。如果每次触发事件都重新初始化oldtime,则时间差始终为0,导致节流失效。闭包的设计确保了oldtime在多次触发中持续更新,而不是被重置。
javascript 复制代码
// 使用时间戳实现
function throttled1(fn, delay = 500) {
    let oldtime = Date.now()
    return function (...args) {
        let newtime = Date.now()
        if (newtime - oldtime >= delay) {
            fn.apply(null, args)
            oldtime = Date.now()
        }
    }
}
// throttled1 被调用一次,oldtime 初始化一次
const throttledFn = throttled1(someFn, 1000);
// 多次触发点击,执行的是闭包函数
element.addEventListener('click', throttledFn); 

// 错误用法会导致节流失败
element.addEventListener('click', () => {
  throttled1(someFn, 1000)(); 
  // 每次点击都调用 throttled1,生成新闭包,oldtime 始终是新的
});


// 使用定时器实现
function throttled2(fn, delay = 500) {
    let timer = null
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args)
                timer = null
            }, delay);
        }
    }
}

// 使用时间戳加定时器实现更加准确的节流
function throttled(fn, delay) {
    let timer = null
    let starttime = Date.now()
    return function () {
        // 当前时间
        let curTime = Date.now() 
        // 从上一次执行到现在还剩下多少时间
        let remaining = delay - (curTime - starttime) 
        let context = this
        let args = arguments
        clearTimeout(timer)
        if (remaining <= 0) {
            fn.apply(context, args)
            starttime = Date.now()
        } else {
            timer = setTimeout(fn, remaining);
        }
    }
}

应用场景

防抖在连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号邮箱验证输入检测
  • 浏览器的resize(浏览器窗口大小改变时触发的事件)。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

  • 浏览器的scroll滚动加载,加载更多或滚到底部监听
  • 搜索框,搜索联想功能

总结

如:浏览器的resize(浏览器窗口大小改变时触发的事件)、scrollkeypress(当用户按下键盘上的字符键时触发的事件)、 mousemove等事件在触发时会不断地调用绑定在事件上的回调函数,极大地浪费资源降低前端性能。 为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率。

相关推荐
mldong2 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排2 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60613 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角4 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!4 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰4 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi6 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen6 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马7 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu7 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss