1.防抖
1.1 概念
对于短时间内连续触发的事件,防抖的含义就是使得某个时间期限内,事件处理函数只执行一次。
防抖的原理就是:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行。
1.2 实现思路
第一次触发事件时,不立即执行函数,而是给出一个时间期限,然后:
- 如果在时间期限内没有再次触发事件,那么就执行函数
- 如果在时间期限内再次触发事件,那么当前计时取消,重新开始计时
因此,即使短时间内大量触发同一事件,也只会执行一次函数。
1.3 实现
实现的关键:
- 用于计时的setTimeOut函数
- 用于保存计时的变量,可以利用闭包实现
因此我们可以写出:
function debounce(func, delay) {
var timeout = null;
return function () {
// 在规定时间间隔达到前 每次触发事件时需要清除已经过时间 重新计时
clearTimeout(timeout);
// 每次触发事件就会新增一个setTimeout开始计时,经过规定时间后执行func()
timeout = setTimeout(() => {
func();
}, delay)
}
}
但是有两个问题需要我们去处理:
- 若此时在func()中打印this,会发现this指向window而非调用者
比如有一个div,鼠标每经过这个div它计数器就通过add()加1,这个场景就可以用防抖处理,此时add()中的this理应指向div更合适做后续处理而非window。
- 不好处理func()传进来的参数
因此作出以下处理:
- 通过apply改变this指向
- 通过arguments处理传入的参数(其实一般就是触发事件时产生的事件对象event)。这里触发事件时debounce返回闭包函数,浏览器就会将事件对象传到闭包函数内。
const debounce = (fn, delay) => {
let timer = null;
return function (e) {
// 每次触发事件先清除之前定时器 重新计时
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
// 让fn的this指向事件触发者,并传入arguments(一般即浏览器对象event)
fn.apply(this, arguments);
}, delay)
}
}
这样完整的防抖函数就完成了,对需要被处理的函数进行处理即可:
// 测试函数
function test(e) {
console.log('防抖', Math.random(), '事件对象', e, '事件触发者', this);
}
//滚动事件
window.addEventListener('scroll', debounce(test,500));
上面的防抖函数已经较为全面,当然可以再升级一下,比如希望先执行函数再等delay,而非上面的先等完delay才能执行函数,通过添加一个immediate参数实现:
function debounce(fn, delay , immediate) {
let timer = null;
return function () {
if (timer) clearTimeout(timer);
// 先执行函数再等delay
if (immediate) {
let callNow = !timer;
timer = setTimeout(() =>{
timer = null;
} , delay)
if(callNow) fn.apply(this , arguments)
// 等完delay再执行函数
} else {
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay)
}
}
}
2.节流
2.1 概念
对于短时间内连续触发的事件,节流的含义就是在函数执行一次后,该函数在指定的时间期限内不再执行,过了这段时间才生效,说白了就是隔一段时间执行一次。
那和防抖有什么区别呢?个人理解是(可能和上面重复,但放在这里对比可能清晰点):
- 防抖:执行完一次之后,就必须经过规定的时间才能执行下一次,如果违反规则提前执行,那就重新计时,直到守规矩为止。
- 节流:执行完一次后,就必须经过规定的时间才能执行下一次,如果违反规则提前执行,那也无所谓,反正到了规定的间隔时间我就执行。
对于防抖和节流一个最主观的判断方法就是:在10s内你疯狂点击一个按钮,如果使用了防抖则会只执行一次,而你使用了节流则会每隔一段时间执行一次,这个时间可以自己来掌控。
2.2 实现
具体语句看注释即可,内容不难理解,同样用上述的测试函数进行测试即可
function throttle(func, delay) {
var canRun = true;
return function (e) {
// 如果还没到规定的时间 那不允许运行 直接返回
if (!canRun) return;
// 已经到了规定时间 可以运行 首先将运行标记置false再执行setTimeout
canRun = false;
setTimeout(() => {
func.apply(this, arguments)
// 此处再把运行标记置true,表示经过delay后时间间隔到了
// 当delay时间没到时时标记恒为false 在开头会被直接return
canRun = true;
}, delay)
}
}
另一种实现方式是利用时间戳来实现:
function throttle(func, delay) {
var previous = 0;
return function () {
var now = +new Date();
if (now - previous > delay) {
func.apply(this, arguments);
previous = now;
}
}
}
+new Date()实现将时间戳转为数字,是数据类型转换那块的内容
3.具体应用场景
- 防抖:适用于无法预知的用户主动行为,如搜索框输入内容,只有在用户输入完后才执行查询请求,这样可以减少请求次数,节约请求资源。
- 节流:适用于非用户主动行为或者可预知的用户主动行为,如鼠标连续不断地触发某事件(如点击),监听计算滚动条的位置等等