Skip to content

节流防抖原理和应用

防抖

  • 原理:事件被触发后不立即触发回调,而是等待 n 秒,如果 n 秒期间事件又被触发则重新计时,并且不会再触发前一事件的回调。

  • 适用场景:

    • 按钮提交场景:防止多次提交按钮,只执行最后一次提交

    • 搜索框联想场景:防止很快打字的时候每个字都发送联想请求,只在用户中断快速输入后发送请求

  • 基础功能,即先防抖后执行

    js
    function debounce(func, wait) {
        let timeout;
        return function() {
            const context = this;
            const args = arguments;
            clearTimeout(timeout);
            timeout = setTimeout(function() {
                func.apply(context, args);
            }, wait);
        };
    }
  • 立即执行,即先执行后防抖

    有时我们希望做的防抖功能是立即执行,而等 n 秒后才能进行下一次触发,其实这个类似于节流

    js
    function debounce(func, wait, immediate) {
        let timeout;
        return function() {
            const context = this;
            const args = arguments;
            if (timeout) clearTimeout(timeout);
            if (immediate) {
                const callNow = !timeout;
                timeout = setTimeout(function() {
                    timeout = null;
                }, wait);
                if (callNow) func.apply(context, args);
            } else {
                timeout = setTimeout(function() {
                    func.apply(context, args);
                }, wait);
            }
        };
    }
  • 返回值功能 func 函数可能有返回值,所以需要返回函数结果,但是当 immediatefalse 的时候,由于使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undifined ,所以只在 immediatetrue 的时候返回函数的执行结果。

    js
    function debounce(func, wait, immediate) {
        let timeout, result;
        return function() {
            const context = this;
            const args = arguments;
            if (timeout) clearTimeout(timeout);
            if (immediate) {
                const callNow = !timeout;
                timeout = setTimeout(function() {
                    timeout = null;
                }, wait);
                if (callNow) result = func.apply(context, args);
            } else {
                timeout = setTimeout(function() {
                    func.apply(context, args);
                }, wait);
            }
            return result;
        };
    }

节流

  • 原理:规定在一个单位时间内,只能触发一次函数,如果这个单位时间内出发多次函数,只有一次生效。

  • 适用场景

    • 拖拽场景:固定时间内只执行一次,放置超高频次触发位置变动

    • 缩放场景:监控浏览器 resize

  • 使用时间戳实现

    • 使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(一开始设置为 0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于设置的时间周期则不执行。
    js
    function throttle(func, wait) {
        let context, args;
        let previous = 0;
        return function() {
            let now = +new Date();
            context = this;
            args = arguments;
            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        };
    }
  • 使用定时器实现

    • 当触发事件的时候,我们设置一个定时器,再次触发事件时如果定时器存在就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下一个定时器。
    js
    function throttle(func, wait) {
        let timeout;
        return function() {
            const context = this;
            const args = arguments;
            if (!timeout) {
                timeout = setTimeout(function() {
                    timeout = null;
                    func.apply(context, args);
                }, wait);
            }
        };
    }

CSS节流

CSS 实现和 JS 的思维不同,这种场景可以理解成是对 CSS 动画的控制,比如有一个动画控制按钮从禁用->可点击的变化,每次点击时让这个动画重新执行一遍,在执行的过程中,一直处于禁用状态,是不是就达到了“节流”的效果了

css
button{
  animation: throttle 2s step-end forwards;
}
button:active{
  animation: none;
}
@keyframes throttle {
  from {
    pointer-events: none;
  }
  to {
    pointer-events: all;
  }
}