如果在angualr项目中实现防抖节流,可以使用封装好的模块,比如rxjs。
但今天说的是使用原始的防抖节流方法遇到的问题:
以节流为例,先贴一段常见的原始节流代码:
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
通常都是以闭包的形式实现节流代码,然后再看触发方式window.addEventListener,网上大多数介绍防抖和节流的例子,都是用window.addEventListener来监听,等待触发的。
然而!应用到angular项目中时,代码是这样的:
search(){
// 节流处理,间隔时间处理一次
this.throttle(func, 1000);
}
坑一:我们期望input输入时,进行节流处理,然而事与愿违,发现throttle()并没有触发,也许聪明的你已经发现,原始代码使用节流方法,是通过回调函数,而我们现在是直接调用,this.throttle(func, wait)的结果只是一个匿名函数,并没有执行!所以应该这样调用:
search(){
// this.throttle(func, wait)返回匿名函数,要进行调用
this.throttle(func, 1000)();
}
坑二:现在函数可以执行了,我们期望无论input怎么触发,每1000ms执行一次,但很快我们发现并没有节流成功!传入的func函数,还是每次触发都会执行!这是为什么?其实中了闭包的坑!先看个闭包的例子:
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); //1 2 3 1 2 3
闭包相关例子可以看着篇:https://blog.csdn.net/weixin_43586120/article/details/89456183
上面的例子可以看出,每次执行outerFn()时,都会开辟一块空间,这就导致变量i的值不能保持连续!结合本例,看一下上面的节流为什么不生效?就是每次input触发,都会执行this.throttle(func, 1000)();而this.throttle(func, 1000)每次执行都新开辟一块空间,这样就导致内部变量canRun 这个标志位每次都会初始化,根本起不到标志位的作用!那么怎么解决呢?重点是this.throttle(func, 1000)只执行一次,把它返回的匿名函数拿到,以后触发执行这个匿名函数就行了!
constructor() {
//构造函数只执行一次,但弊端还是会引入了一个全局变量,不过这不是本文讨论的重点
this.fun = this.throttle(this.func, 1000);
}
search(){
// 每次执行返回匿名函数
this.fun();
}
这样每次执行this.fun()时,每次取标志位变量,就是从同一个地址去获取,当然就生效了!其实这个问题是闭包引起的,如果不使用闭包,不考虑全局变量污染,那么实现节流就不会遇到这个问题。
最后,回到最初的原始代码示例,window.addEventListener('resize', throttle(sayHi))这样监听回调为什么没有这个问题?因为throttle(sayHi)只执行了一次,就是在执行window.addEventListener('resize', throttle(sayHi))语句时执行了一次,把返回的函数作为回调函数,每次resize触发,就会执行已经返回的回调函数!