Wscats / node-tutorial

:relaxed:Some of the node tutorial -《Node学习笔记》
https://github.com/Wscats/node-tutorial
542 stars 94 forks source link

函数节流和函数去抖 #53

Open Wscats opened 5 years ago

Wscats commented 5 years ago

去抖

函数调用 n 秒后才会执行,如果函数在 n 秒内被调用的话则函数不执行,重新计算执行时间,这里的原理是利用了一个闭包,每当有事件被监听就清除闭包中timer,重新赋值

// debounce
function debounce(method, delay) {
    var timer = null;
    return function () {
        var context = this,
            args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function () {
            method.apply(context, args);
        }, delay);
    }
}

比如resize()一瞬间调用了很多次,只会延迟触发最后一次调用,再比如用户在短时间触发了很多次输入动作oninput,就会延迟只触发最后一次,我们可以把它用在scroll、resize和input这些事件监听上,减少不必要的性能开销,还可以在下列事件下做去抖处理

function resizeHandler() {
    console.log(this);
}
window.onresize = debounce(resizeHandler, 500);

function inputHandler() {
    console.log(this);
}
document.getElementById("input").oninput = debounce(inputHandler, 500);

更进一步,我们不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行 这里增加一个 immediate 参数来设置是否要立即执行

function debounce(func, delay, immediate) {
    var timer = null;
    return function () {
        var context = this;
        var args = arguments;
        if (timer) clearTimeout(timer);
        if (immediate) {
            //根据距离上次触发操作的时间是否到达delay来决定是否要现在执行函数
            var doNow = !timer;
            //每一次都重新设置timer,就是要保证每一次执行的至少delay秒后才可以执行
            timer = setTimeout(function () {
                timer = null;
            }, delay);
            //立即执行
            if (doNow) {
                func.apply(context, args);
            }
        } else {
            timer = setTimeout(function () {
                func.apply(context, args);
            }, delay);
        }
    }
}

节流

函数预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期

// throttle
function throttle(method, duration) {
    var begin = new Date();
    return function () {
        var context = this,
            args = arguments,
            current = new Date();
        if (current - begin >= duration) {
            method.apply(context, args);
            begin = current;
        }
    }
}
- 作用
防抖动 将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
节流 使得一定时间内只触发一次函数。 它和防抖动最大的区别就是,节流函数不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖动只是在最后一次事件后才触发一次函数。

Vscode 的写法

vscode/extensions/git/src/decorators.ts

import { done } from './util';

function decorate(decorator: (fn: Function, key: string) => Function): Function {
    return (_target: any, key: string, descriptor: any) => {
        let fnKey: string | null = null;
        let fn: Function | null = null;

        if (typeof descriptor.value === 'function') {
            fnKey = 'value';
            fn = descriptor.value;
        } else if (typeof descriptor.get === 'function') {
            fnKey = 'get';
            fn = descriptor.get;
        }

        if (!fn || !fnKey) {
            throw new Error('not supported');
        }

        descriptor[fnKey] = decorator(fn, key);
    };
}

function _memoize(fn: Function, key: string): Function {
    const memoizeKey = `$memoize$${key}`;

    return function (this: any, ...args: any[]) {
        if (!this.hasOwnProperty(memoizeKey)) {
            Object.defineProperty(this, memoizeKey, {
                configurable: false,
                enumerable: false,
                writable: false,
                value: fn.apply(this, args)
            });
        }

        return this[memoizeKey];
    };
}

export const memoize = decorate(_memoize);

function _throttle<T>(fn: Function, key: string): Function {
    const currentKey = `$throttle$current$${key}`;
    const nextKey = `$throttle$next$${key}`;

    const trigger = function (this: any, ...args: any[]) {
        if (this[nextKey]) {
            return this[nextKey];
        }

        if (this[currentKey]) {
            this[nextKey] = done(this[currentKey]).then(() => {
                this[nextKey] = undefined;
                return trigger.apply(this, args);
            });

            return this[nextKey];
        }

        this[currentKey] = fn.apply(this, args) as Promise<T>;

        const clear = () => this[currentKey] = undefined;
        done(this[currentKey]).then(clear, clear);

        return this[currentKey];
    };

    return trigger;
}

export const throttle = decorate(_throttle);

function _sequentialize(fn: Function, key: string): Function {
    const currentKey = `__$sequence$${key}`;

    return function (this: any, ...args: any[]) {
        const currentPromise = this[currentKey] as Promise<any> || Promise.resolve(null);
        const run = async () => await fn.apply(this, args);
        this[currentKey] = currentPromise.then(run, run);
        return this[currentKey];
    };
}

export const sequentialize = decorate(_sequentialize);

export function debounce(delay: number): Function {
    return decorate((fn, key) => {
        const timerKey = `$debounce$${key}`;

        return function (this: any, ...args: any[]) {
            clearTimeout(this[timerKey]);
            this[timerKey] = setTimeout(() => fn.apply(this, args), delay);
        };
    });
}

参考文档