防抖 debounce & 节流 throttle

10 阅读2分钟

防抖(debounce)

防抖是一种优化技术,用于限制某些高频率事件(窗口大小调整,滚动,输入框输入调用后台接口等)的触发次数。设置一个给定的延迟时间,如果在这个延迟时间内该事件没有再次触发,则重新开始计时,在延迟时间结束后,事件才会被真正处理。

例如电梯关门需要5秒后执行,进来一个人,然后就需要等待5秒,再进来一个人再等待5秒,这就是防抖。

function debounce(func, wait) {
    let timeout;
    
    return function (...args) {
        // context 保存了返回函数调用时的 this 值
        const context = this;

        clearTimeout(timeout);

        timeout = setTimeout(() => {
            func.apply(context, args);
        }, wait);
    };
}

理解上述方法

  • 如果timeout不为空,表示正在执行setTimeout,但是还没有执行到回调函数,此时要清除计时器
  • 进而取消执行setTimeout
  • 最后一次执行,执行了一个新的定时器,此时在等待时间内没有新的事件触发,定时器到期,执行回调函数。
// 使用
const obj = {
    value: 0,
    increment() {
        this.value++;
        console.log(this.value);
    }
};

const debouncedIncrement = debounce(obj.increment, 500);

obj.debouncedIncrement = debouncedIncrement;
// 调用防抖方法,this指向 obj
obj.debouncedIncrement(); // 500毫秒后输出1

我们需要保存当前的this,如果不保存的话,在下面这个例子中,this就会指向window对象,这样就无法调用this.value了。

具体执行步骤及其细节

  • 14行(第二个代码片段)中,调用防抖,此时this指向obj
  • 在上述的防抖方法中(第一个代码片段,第6行),context此时被赋值为obj
  • 当500毫秒过去后,执行func.apply(context, args)
  • func 是 obj.increment
<input type="text" id="searchBox" placeholder="Type to search...">

function handleSearch(event, extraParam) {
  console.log('Searching for:', event.target.value);
  console.log('Extra parameter:', extraParam);
}

const searchBox = document.getElementById('searchBox');
const debouncedSearch = debounce(handleSearch, 500);

searchBox.addEventListener('input', (event) => debouncedSearch(event, 'someExtraValue'));
  • 用户在输入框中输入时,每次输入都会触发 input 事件,调用 debouncedSearch 函数。

  • debouncedSearch 函数捕获所有传递的参数(在这个例子中是 event 对象和 'someExtraValue')。

  • 当定时器到期后,调用 fn.apply(this, args),确保 handleSearch 函数接收到正确的参数。

  • handleSearch 函数在 500 毫秒后被执行,并输出 event.target.value 和 'someExtraValue'

节流(throttle)

节流会确保在制定的时间间隔内发生至少一次。
这个代码比较好理解,时间间隔大于wait的时候,就可以执行一次

function throttle(func, wait) {
    let lastTime = 0;
    return function (...args) {
        const now = Date.now();
        if (now - lastTime >= wait) {
            lastTime = now;
            func.apply(this, args);
        }
    };
}

有点类似于平常写的,防止重复点击的操作

function throttle(fn, wait) {
    let bool = true
    return function (...arg) {
        if (!bool) return
        bool = false
        setTimeout(() => {
            fn.call(this, arg)
            bool = true
        }, wait)
    }
}