跳到主要内容

防抖与节流

这篇文章的内容来自这篇博客

节流函数

function throttle(fn, interval = 200) {
let flag = null;
return function(...args) {
if (!flag) {
flag = true;
setTimeout(() => {
flag = false;
fn.call(this, ...args);
}, interval);
}
}
}

防抖函数

js

function debounce(fn, delay = 200) {
if (typeof fn !== 'function') { // 参数类型为函数
throw new TypeError('fn is not a function');
}

let lastFn = null;
return function(...args) {
if (lastFn) {
clearTimeout(lastFn);
}
let lastFn = setTimeout(() => {
lastFn = null;
fn.call(this, ...args);
}, delay);
}
}

js-使用方式

var o = {
c: 1,
a: function() {
console.log(this.c);
}
}

o.b = debounce(o.a);

ts/angular

export class PollingTaskUtils {
constructor(){}
// ts时,我把{ngOnDestroy}改成any
static tag(component: {ngOnDestroy}, tag: string = 'default'): PollingMgr {
let taskTag = `__${tag}__`;
if (component[taskTag] == null) {
let pollingMgr = new PollingMgr(component, taskTag);
component[taskTag] = pollingMgr;
}
return component[taskTag];
}
}

export class PollingMgr {
private readonly AUTO_CLEAR_FLAG_PREFIX = '__auto_clear_flat';

private _delay: number = 0; // 任务的延迟时长
private _isLoop: boolean = false; // 是否是循环任务
private _interval: number; // 循环任务的间隔
private _resolve: {loop: (interval?: number) => any}
private _pollingTask: (resolve?: {loop: (interval?: number) => any}) => any;
private _pollingFlag = null;
private _pollingIntervalFlag = null;

constructor(protected component: {ngOnDestroy}, protected tag: string) {
this._resolve = {
loop: (interval?: number) => {
if (interval > 0) {
this._isLoop = true;
this._interval = interval;
} else {
this._isLoop = false;
}
this._handleLoop();
}
};
}

run(task: (resolve?: {loop: (interval?: number) => any}) => any) {
this._clear(this.component);
this._pollingTask = task;
if (this._delay) {
this._pollingFlag = setTimeout(() => {
this._pollingFlag = null;
task.apply(this.component, [this._resolve]);
}, this._delay);
} else {
task.apply(this.component, [this._resolve]);
}
this._handleAutoClear();
}

clear(component?: {ngOnDestroy}) {
this._clear(component);
}

delay(dealy: number): PollingMgr {
this._delay = delay;
return this;
}

runInterval(task: () => any, interval: number) {
if (!this._pollingIntervalFlag) {
this._pollingIntervalFlag = setInterval(() => {
task.apply(this.component);
}, interval);
}
this._handleAutoClear();
return this._pollingIntervalFlag;
}

private _handleLoop() {
if (this._isLoop) {
this._clear(this.component);
this._pollingFlag = setTimeout(() => {
this._pollingFlag = null;
this._pollingTask.apply(this.component, [this._resolve]);
}, this._interval);
}
}

private _handleAutoClear() {
if (this.component[this.AUTO_CLEAR_FLAG_PREFIX + this.tag] == null) {
this.component[this.AUTO_CLEAR_FLAG_PREFIX + this.tag] = true;
let originFun = this.component['ngOnDestroy'];
this.component['ngOnDestroy'] = (): void => {
originFun.apply(this.component);
delete this.component[this.tag];
delete this.component[this.AUTO_CLEAR_FLAG_PREFIX + this.tag];
this._pollingTask = null;
this._clear(this.component);
if (this._pollingIntervalFlag) {
clearInterval(this._pollingIntervalFlag);
this._pollingIntervalFlag = null;
}
};
}
}

private _clear(component: {ngOnDestroy}) {
this._isLoop = false;
if (this._pollingFlag) {
clearTimeout(this._pollingFlag);
this._pollingFlag = null;
}
}
}

ts-使用方式

/**
* 轮询、延迟、防抖的任务工具类
* 入口接收两个参数:
* component:当前的组件类,使用时必须挂载在某个组件上,在组件销户时,如果有轮询任务,会去进行释放定时器
* tag:可选参数,用于标识不同的任务,相同的 tag,多次调用都会被视为同个任务进行防抖处理
*/
PollingTaskUtils.tag(component, tag?: string);

// 1. 延迟任务用法,比如延迟5s后处理
PollingTaskUtils.tag(this).delay(5000).run(() => {
// do something
});
// 因为 tag 没传,该任务会和上面的被视为同个任务,如果上个任务延迟未被执行,则先取消,以下面为主
PollingTaskUtils.tag(this).delay(5000).run(() => {
// do something
});
// tag 参数指定为 'task',表示一个新的任务,会上述的延迟任务相互独立
PollingTaskUtils.tag(this).delay(5000, 'task').run(() => {
// do something
});

// 2. 轮询任务,比如每隔 10s 发起一次请求
PollingTaskUtils.tag(this).run(resolve => {
// 模拟请求
setTimeout(() => {
// do something
resolve.loop(10000); // 设置轮询间隔
}, 2000)
});

// 3. 轮询任务,符合一定条件停止轮询
PollingTaskUtils.tag(this).run(resolve => {
// 模拟请求
setTimeout(() => {
if (flag) {
resolve.loop(-1); // <=0 或者不调用时停止轮询
} else {
resolve.loop(3000);
}
}, 2000);
});

// 4. 防抖处理
let i = 0;
while(i++ < 10) {
PollingTaskUtils.tag(this).delay(500).run(() => {
// do something
});
}

// 5. 由于 run 内部是通过 setTimeout 来实现轮询任务,但这个并不精准,当要求较精准的轮询时,比如时钟,使用 setInterval 会比较精准
PollingTaskUtils.tag(this).runInterval(() => {
// do something
}, 1000);