前端自查--防抖和节流

107 阅读3分钟

防抖

指触发事件后在规定时间后执行一次。如果在规定时间内又执行了,则会重新计算规定时间
理解:公交站等车,规定需要等到10分钟发车,但是如果10分钟内一直有人上车,那么需要重新等待10分钟,直到这10分钟内没有人上车,就发车。

实现思路:

1. 事件第一次触发时,timeout为null,调用later后,若immedaite为true,那么立即调用fn。如果immediate为false,那么经过wait秒后,调用fn
2.事件第二次触发,如果timeout为null,就执行第一步。如果不为null,即在wait时间内又触发这个时候需要清空定时器,重新开始计时。

function debounce(fn, wait, immediate) {
    let timeout;
    const later = function(context, args) {
        setTimeout(() => {
            timeout = null;
            if (!immediate) {
                fn.call(context, args);
                context = null;
                args = null;
            }
        }, wait);
    };
    const debounce = function(... args) {
        if (!timeout) {
            timeout = later(this, args);
            if (immediate) {
                fn.apply(this, args);
            }
        } else {
            clearTimeout(timeout);
            timeout = later(this, args);
        }
    };
    debounce.cancel = () => {
        clearTimeout(timeout);
        timeout = null;
    };
    return debounce;
}
function handle() {
    console.log(Math.random());
}
window.addEventListener("mousemove", debounce(handle, 1000, true)); // 调用立即执行版本
window.addEventListener("mousemove", debounce(handle, 1000, false)); // 调用非立即执行版本

节流

在规定的时间内只执行一次,如果这个时间内多次触发,只能有一次生效
理解: 公交车站,需要等到10分钟发车,不管这10分钟有多少乘客,只要到了10分钟,就发车

立即执行

使用时间戳:

//立即执行function throttle(fn, wait) {
    let previous = 0;
    return function(... args) {
        const now = new Date();
        if (now - previous > wait) {
            fn.apply(this, args);
            previous = now;
        }
    };
}

非立即执行

使用定时器:

//非立即执行,第一次不会立马执行,但是会执行最后一次
function throttle(fn, wait) {
    let timeout;
    return function(... args) {
        if (timeout) {
            return false;
        }
        timeout = setTimeout(() => {
            fn.apply(this, args);
            timeout = null;
        }, wait);
    };
}

立即执行&非立即执行

实现思路:

1.  第一次执行时候,需要立马执行。 这里重要点就是wait - (now-previous)这个remaining时间。第一次remaing肯定是小于0的。这个时候就需要立马执行函数fn,如果这个时候有定时器,就需要清空定时器,然后执行fn函数

2. 第二次执行,如果remaing时间小于0,就执行第一步。如果remaing 大于0 ,这个时候判断是否有定时器,如果有,不处理,等待执行就好,如果没有就重新定义个定时器。

function throttle(fn, wait) {
    let previous = 0;
    let remaining; let timeout;
    const throttle = function(... args) {
        const now = +new Date();
        remaining = wait - (now - previous);
        if (remaining < 0) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null; 
           }
            previous = now;
            fn.apply(this, ... args);
        } else {
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    previous = +new Date();
                    fn.apply(this, ... args);
                }, remaining);
            }
        }
    };
    throttle.cancel = function() {
        previous = 0;
        clearTimeout(timeout);
        timeout = null;
    };
    return throttle;
}
function handle() {
    console.log(Math.random());
}
window.addEventListener("mousemove", throttle(handle, 1000));

优化

添加参数控制首尾是否执行

function throttle(fn, wait, options = {}) {
    let previous = 0;
    let remaining; let timeout;
    const { leading, trailing } = options;
    const isLeading = typeof leading === "boolean" ? leading : true;
    const isTrailing = typeof trailing === "boolean" ? trailing : true;
    const throttle = function(... args) {
        const now = +new Date();
        if (!isLeading && !previous) {
            previous = now;
        }
        remaining = wait - (now - previous);
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            fn.apply(this, ... args);
        } else {
            if (!timeout && isTrailing) {
                timeout = setTimeout(() => {
                    timeout = null;
                    previous = !isLeading ? 0 : +new Date();
                    fn.apply(this, ... args);
                }, remaining);
            }
        }
    };    
throttle.cancel = function() {
        previous = 0;
        clearTimeout(timeout);
        timeout = null;
    };
    return throttle;
}
function handle() {
    console.log(Math.random());
}
window.addEventListener("mousemove", throttle(handle, 1000));