防抖是事件停止触发且过了某指定时间后执行一次,而函数节流是间隔某指定的单位时间执行一次。(控制高频事件执行次数)
debounce防抖
事件被触发后延迟n秒再执行回调,如果在这n秒内又被触发,则重新计时。
实现
具体实现:原理就是利用闭包
inp.oninput = debounce(function (event){
console.log(this.vlaue);
},500)
function debounce(fn, delay) {
let timerId = null;
return function (...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
//fn()独立函数调用this就是window
//这个this指向触发事件的对象
fn.apply(this,args);
timerId = null;
}, delay);
}
}
场景
resize事件、scroll事件、用户输入input事件、拖拽事件、频繁的点击等
throttle节流
顾名思义就是每过n秒仅执行一次回调函数。如单位时间内多次触发函数,也只有第一次生效,相比防抖缺少了clearTimeout这一步骤,而是通过if语句判断
实现
// 节流函数
function throttle(fn, delay) {
let timer = null; //定义一个定时器
return function(...args) {
if(!timer) {
timer = setTimeout(()=> {
fn.apply(this, args);
timer = null;
}, delay);
}
}
}
// 原始函数
const scrollEvent = function() {
console.log('当前时间戳:' + new Date().getTime());
}
// 两函数结合,实现节流防抖
const scrollHandler = throttle(scrollEvent, 1000)
// 滚动事件
window.addEventListener('scroll', scrollHandler);
由于setTimeout函数的时间参数存在误差(或者说执行函数本身所需要的时间),所以打印的结果后三位不一定是我们期望的数值。节流函数除了利用定时器的方式,也可以利用时间戳的方式。当前时间与上一个时间进行比对,相差结果是我们希望看到的。
场景
鼠标不断触发某事件时,如点击,只在单位事件内触发一次;
懒加载时要监听计算滚动条的位置,但不必要每次滑动都触发,可以降低计算频率,而不必要浪费CPU资源
关于其中的apply使用
在学习手撕节流防抖的过程中我发现两者都使用到了apply改变this指向,我产生了这样的疑惑——“直接调用不行吗,好像影响也不是很大”,然后查阅了相关的博客,可以得出这样的总结:
首先我们需要知道其用意,apply希望this指向哪里:
fun.apply(this,arguments)的用意,无非就是想fun中的this指向debounce中return的这个函数中的this,return回来的这个函数中的this也就是指向直接调用return 函数那个对象
为什么需要改变this呢,不改变不行吗:
上述例子中我们都可以知道,这里都是window来直接调用return回来的那个函数,所以apply与否都没差
而对于下面这种情况:
不改变的话出大问题,不改变的话指向undefined(严格模式下没有调用者时,this的值为undefined),加上apply后才指向实例。
class Foo {
constructor() {
this.a = "a";
this.bar = debounce(this.bar, 500);
}
bar() {
console.log(this.a);
}
}
const foo = new Foo();
foo.bar();
如有不对欢迎探讨!
参考资料:
防抖和节流的区别和实现详解(中高级前端面试必备知识)
防抖/节流中的apply起到的作用
this指向,防抖函数中的fn.apply(this,arguments)作用