参考文章: https://www.cnblogs.com/cc-freiheit/p/10827372.html
防抖
- 短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。
- 分为立即执行和非立即执行;
非立即执行;
非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
function debounce(func, wait) {
let timer;
return function() {
let context = this; // 注意 this 指向
let args = arguments; // arguments中存着e
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
function count(a, b) {
console.log(a, b)
}
let debounceCount = debounce(count,1000);
setInterval(() => {
debounceCount(1, 2)
}, 100);
代码解析:
- debounce函数return一个新的函数,利用arguments将debounceCount的参数传给func(count)中
- debounce函数作用域存着一个timer(闭包),每次调用debounceCount时如果有timer就clear掉,然后延期执行func.apply(this, args);
- 这儿胡this指向哪儿呢? 指向的是debounceCount函数执行的作用域, 也就是外部作用域。那么为什么要this呢? 不太清楚。 嗯 , 知道了, 是为了content.onmousemove 类似的指向对象的,同时, 也为了在debounceCount中可以访问外部的变量, 没有this,就访问不到了。
立即执行
立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果
// 合成版
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行, false - 延迟执行
*/
function debounce(func, wait, immediate) {
let timer;
return function() {
let context = this,
args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply
}, wait)
}
}
}
代码解析:
- 大致和非立即执行差不多,函数传入参数为立即执行时,如果callNow 执行,那么合适为callNow为true呢? 没有timer的时候, 也就是说有timer就不执行,首次调用没timer,在有timer的时候调用将更新timer = null的延迟, wait秒后, timer = null, 又变成首次的状态。
节流(throttle)
- 连续触发事件但是在 n 秒中只执行一次函数。即 2n 秒内执行 2 次… 。
- 时间戳和定时器版。
时间戳
在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。最后一次可能会被过滤掉
// 时间戳版
function throttle(func, wait) {
let previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
代码解析:
- previous是上次的时间戳,返回一个函数, 如果now - previous > wait, 就可以执行func.apply(context, args); 同时更新previous为now,为下次做准备。
定时器版
在持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次。
// 定时器版
function throttle(func, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
代码解析:
- 方法延迟1s后执行, 延迟一秒后timer设置为null, 在有定时器期间, 不处理任何事情,
- 和防抖的区别是什么呢?非立即执行的防抖是,每次点击clearTimeout,直接更新timeout, 不加判断,这儿就加了个判断
- 那么和立即执行函数的区别呢?立即执行函数也加了判断,立即执行函数是 先赋值一个callNow是上次的!timer,然后设置定时器将timer变为null,接着根据callNow判断要不要调用func, 那么可不可以
先if (!timer) func.apply(context, args); 再 设置 timer 呢? 也就是下面的代码, 是可以的
if (!timer) func.apply(context, args);
// 还有这儿为什么不加clearTimeout呢?是不是之前的也不用加clear
// 经过测试不clear的话会执行两次func,timer = null不会清空上一个定时器, 设置timer为null后, 第二次还是会在设置timer为null,那么我们的func呢,
// timer 在1s内被设置为null, .5s时又调了一次, 1stimer会变成null, 1.5s他也会变成null, 所以定时器多了,后timer会被频繁的设置为null, 也就是func可以频繁的被执行。 他会所以这儿他有个bug应该clearTimeout
clearTimeout(timer)
timer = setTimeout(() => {
timer = null;
}, wait);
时间戳版本与定时器版本的结合, 也就是传参而已。。。
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 表时间戳版,2 表定时器版
*/
function throttle(func, wait, type) {
if (type === 1) {
let previous = 0;
} else if (type === 2) {
let timeout;
}
return function() {
let context = this;
let args = arguments;
if (type === 1) {
let now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}