很多时候,用户在交互过程中有时候操作需要异步处理,或者需要等待,不能让用户频繁交互,或者防止客户端和服务器频繁交互都会做出一些限制,其中常用的操作是函数防抖和函数节流,对于函数做一些处理后,让用户的行为效果延迟发送,或者在一定期间内只能发送一次。Typescript的函数是支持装饰器的,下面就介绍一下如何通过装饰器来让函数防抖和节流。
防抖Debounce和节流throttle
函数防抖和函数节流:优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize
、scroll
,鼠标的mousemove
、mouseover,input输入框的keypress等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制。
首先我们介绍函数防抖:
函数防抖(debounce
)
防抖概念:在事件被触发n时间段后再执行回调,如果在这n时间段内又被触发,则重新计时。就是将多次高频操作优化为只在最后一次执行(某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次)
防抖原理:是维护一个计时器,在规定的delay
时间后触发函数,但是在delay
时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
1 2 3 4 5 6 7 8 9 10
| function debounce(fn, delay) { var timer = null; return function() { timer && clearTimeout(timer) timer = setTimeout(function() { fn.apply(this) }, delay) } }
|
函数节流(throttle
)
节流概念:节流就是每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作。简单说:每隔一段时间,只执行一次函数。
节流原理: 有定时器 版本 和 世界戳版本
- 定时器版本的节流函数其重点是利用闭包保存
timer
变量
- 时间戳版本的节流函数重点是利用闭包保存上一次的时间
previous
定时器
1 2 3 4 5 6 7 8 9 10 11 12
| function throttle(fn, wait) { let timer = null; return function(...args) { if (!timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; }, wait) } } }
|
时间戳
1 2 3 4 5 6 7 8 9 10 11 12 13
| function throttle(fn, wait) { let previous = 0; return function(...args) { let now = +new Date(); if (now - previous > wait) { previous = now; fn.apply(this, args); } } }
|
TypeScript装饰器
TypeScript 中的装饰器使用 @expression 这种形式,expression 求值后为一个函数,它在运行时被调用,被装饰的声明信息会被做为参数传入。
装饰者模式就是动态的给类或对象增加功能的设计模式,TypeScript 中的装饰器可以被附加到类声明、方法、 访问符(getter/setter)、属性和参数上。
命令行 编译文件时:
1
| tsc --target ES5 --experimentalDecorators mytest.ts
|
配置文件 tsconfig.json
1 2 3 4 5 6
| { "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
|
装饰器实际上就是一个函数,在使用时前面加上 @ 符号,写在要装饰的声明之前,多个装饰器同时作用在一个声明时,可以写一行或换行写:
1 2 3 4 5 6 7 8
| @testA @testB declaration
@testA @testB ... declaration
|
作用在类的方法上,有静态方法和原型方法。作用在静态方法上,装饰器函数接收的是类构造函数;作用在原型方法上,装饰器函数接收的是原型对象。装饰器器函数(方法)需要保留(target
,key
,descriptor
)三个参数—原型对象、方法名、描述对象。
下面写个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function throttle(limit: number = 0) { let flag = true return (target: any, key: string, descriptor: any) => { let func = descriptor.value descriptor.value = async (...args: any[]) => { if(!flag) return flag = false try { func.apply(this, ...args) } catch (error) { console.log(error) } if (!limit) return flag = true setTimeout(() => { flag = true }, limit); } } }
class Test{ @throttle(1000) fn() { console.log('fn') } }
const test = new Test() setInterval(test.fn,50);
|
装饰器版本
话不多说,最后看一下最终代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
export const Debounce = (wait: number, immediate: boolean = true)=>{ return function (target: any, key: string, descriptor: any) { let timer = null; const fn = descriptor.value; descriptor.value = async function (...args: any[]) { console.log("Debounce timer", timer); if (immediate) { if (!timer) { fn.apply(this, args); timer = setTimeout(function () { timer = null; }, wait); }else { timer && clearTimeout(timer); timer = setTimeout(()=>{ fn.apply(this, args); }, wait); } } else { timer && clearTimeout(timer); timer = setTimeout(()=>{ fn.apply(this, args); }, wait); } }; return descriptor; }; };
export const Throttle = (wait: number)=>{ return function (target: any, key: string, descriptor: any) { let timer = null; let fn = descriptor.value; descriptor.value = async function (...args: any[]) { console.log("Throttle timer", timer); if (!timer) { fn.apply(this, args); timer = setTimeout(()=>{ timer = null; }, wait); } }; }; };
|
游戏内使用:
1 2 3 4 5 6 7 8 9 10 11
| @Debounce(2000) private testTouchHandler(touchDebounce,data){ console.log("-----------Debounce") console.log("Debounce:" + data + ":" +JSON.stringify(Date.now())); }
@Throttle(2000) private testTouchHandler2(touchThrottle,data){ console.log("-----------Throttle") console.log("Throttle:" +data+ ":" +JSON.stringify(Date.now())); }
|
以上代码看起来整洁,而且实现了函数防抖和函数节流。
动态添加和取消版本
最后再放出一个可以动态添加和取消的防抖和节流的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
public static debounce(func: Function, wait: number = 100, immediate: boolean = false) { let timeoutID: number = 0, result: any; let debounced: FunDebounceOrThrottle = function () { clearTimeout(timeoutID); let arg = arguments; if (immediate) { if (!timeoutID) result = func.apply(func, arg); timeoutID = setTimeout(() => { timeoutID = 0; }, wait); } else { timeoutID = setTimeout(() => { func.apply(func, arg); }, wait); } return result; }; debounced.cancel = function () { clearTimeout(timeoutID); timeoutID = 0; }; return debounced; }
public static throttle(func: Function, wait: number, options: IThrottleOptions = {leading: true, trailing: true}) { let timeoutID: number = 0; let previous = 0;
let throttled: FunDebounceOrThrottle = function () { let now = new Date().getTime(); if (!previous && !options.leading) previous = now; let remaining = wait - (now - previous); let arg = arguments; if (remaining <= 0 || remaining > wait) { if (timeoutID) { clearTimeout(timeoutID); timeoutID = 0; } previous = now; func.apply(func, arg); } else if (!timeoutID && options.trailing) { timeoutID = window.setTimeout(() => { previous = !options.leading ? 0 : new Date().getTime(); timeoutID = 0; func.apply(func, arg); }, remaining); } }; throttled.cancel = function () { clearTimeout(timeoutID); previous = 0; timeoutID = 0; }; return throttled; }
|
现在,动态添加和取消也有了,装饰器版本也有了,一下子学习这么多,想必一定很累吧,爽歪歪,完美收工!
2024年5月15日更新
CreatorExtensions.ts
装饰器代码更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
const delayDefault = 500;
export const debounce = (wait: number = delayDefault, immediate: boolean = true): MethodDecorator => { return function (target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor { let timer: number | null = null; const fn = descriptor.value!; descriptor.value = async function (this: any, ...args: any[]) { if (immediate) { if (!timer) { fn.apply(this, args); timer = setTimeout(() => { timer = null; }, wait); } else { timer && clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, wait); } } else { timer && clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, wait); } }; return descriptor; } as MethodDecorator; };
export const throttle = (wait: number = delayDefault): MethodDecorator => { return function (target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor { let timer: number | null = null; const fn = descriptor.value!; descriptor.value = async function (this: any, ...args: any[]) { if (!timer) { fn.apply(this, args); timer = setTimeout(() => { timer = null; }, wait); } }; return descriptor; } as MethodDecorator; };
|
参考