01 防抖&节流

52 阅读3分钟

防抖是什么

防抖就是对于频繁触发的事件设定一个最小触发间隔,如果触发间隔小于设定的间隔,则清除原来的定时,重新设定新的定时;如果触发间隔大于设定间隔,则保留原来的定时,并设置新的定时;防抖的结果就是一段时间内频繁的触发转变为一次触发

哪些场景需要防抖

在频繁触发事件的场景,有些情况可能执行的逻辑比较复杂或者耗时,此时浏览器的处理跟不上触发,就会发生卡顿、假死或者事件堆积,这里防抖就可以一定程度上解决或者缓解这种故障。

常见的需要防抖的场景: 搜索框keyup、keydown等触发后台请求; 频繁改变窗口大小resize;类似以上频繁触发但是通常只在乎最终结果的情况

防抖实现

基础实现

const debounce = (fn, wait) => {
    let timer;
    return function() {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments);
        }, wait);
    }
};

如果想让事件立即触发一次呢?

const debounce = (fn, wait, immediate = false) => {
    let timer;

    return function() {
        if(timer) clearTimeout(timer);
        if(immediate) {
            let trigger = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, wait);

            if(trigger) {
                fn.apply(this, arguments);
            }
            return;
        }

        timer = setTimeout(() => {
            fn.apply(this, arguments);
        }, wait);
    }
};

如果这个防抖时间比较长,希望可以取消这种“挂起”状态呢?

const debounce = (fn, wait, immediate = false) => {
    let timer;
    const debounced =function(){
        if(timer) clearTimeout(timer);
        if(immediate) {
            let trigger = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, wait);

            if(trigger) {
                fn.apply(this, arguments);
            }
            return;
        }

        timer = setTimeout(() => {
            fn.apply(this, arguments);
        }, wait);
        return;
    }
        
    debounced.cancel = () => {
        clearTimeout(timer);
        timer = null;
    }
    return debounced;
};

节流是什么

节流就是持续触发的事件,每隔一段时间触发一次。

哪些场景需要节流?

在频繁触发事件的场景,有些情况可能执行的逻辑比较复杂或者耗时,此时浏览器的处理跟不上触发,就会发生卡顿、假死或者事件堆积,为了解决这个故障,节流是其中之一的策略

常见的需要防抖的场景: 页面滚动事件监听;mousemove事件触发;类似以上频繁触发但是无需每次触发都需要结果的场景。

实现节流

版本1,设定间隔,如果当前请求触发时间和上次触发间隔小于设定间隔,则不触发


const throttle = (fn, wait) => {
    let pre = 0, context, args;

    return function() {
        context = this;
        args = arguments;
        const now = new Date().getTime();
        if(now - pre > wait) {
            fn.apply(context, args);
            pre = now;
        }
    }
};

版本2,使用定时器;首次触发设定新的定时器,如果间隔小于预设,则清除旧的定时器;

const throttle = (fn, wait) => {
    let context, args, timer;

    const run = () => {
        timer = setTimeout(() => {
            fn.apply(context, args);
            clearTimeout(timer);
            timer = null
        }, wait);
    }

    return function() {
        context = this;
        args = arguments;
        if(!timer) {
            run();
        }
    }
};

版本3 首次触发事件时立即执行一次,在事件触发结束后再执行一次

const throttle = (fn, wait) => {
    let pre = 0, context, args, timer;

    const run = () => {
        pre = Date.now();
        fn.apply(context, args);
        clearTimeout(timer);
        timer = null;
    }

    return function() {
        context = this;
        args = arguments;
        const now = Date.now();
        const remain = wait - (now - pre);
        if(Math.abs(remain) > wait || remain < 0) {
            pre = Date.now();
            fn.apply(context, args);
        } else if(!timer){
            timer = setTimeout(run, wait);
        }
    }
};