函数的防抖和节流
在“高频”触发的场景下,需要进行防抖和节流
- 防抖
- 狂点一个按钮
- 轮播图左右切换
- …
- 节流
- 页面滚动 :默认情况下,页面滚动中:浏览器在最快的反应时间内(4~6MS),就会识别监听一次事件触发,把绑定的方法执行,这样导致方法执行的次数过多,造成不必要的资源浪费
- 输入模糊匹配 (模糊搜索)
- …
高频 : 我们自己设定,多长的时间内,触发两次及以上就算“高频”:封装方法的时候需要指定这个频率(可以设置默认值)
防抖(debounce): 在某一次高频触发下,只识别一次(可以控制开始触发,还是最后一次触发),比如:假设我们规定500ms触发两次以上算高频,那么,只要检测到是高频触发了,则在本次频繁操作下(哪怕操作了10min),只触发一次。
节流(throttle): 在某一次高频触发下,我们不是只识别一次,按照我们设定的时间间隔(自己规定的频率),每到达这个频率就会触发一次,比如:假设我们规定频率是500ms,我们操作了10min,触发的次数 = (10601000)/500
防抖和节流的原理:利用闭包的柯理化函数思想实现函数的防抖和节流
防抖(debounce)
业务场景中处理的技巧1:标识判断
let flag = false;
submit.onclick = function () {
if (flag) return;
flag = true;
console.log('OK');
setTimeout(() => {
// 事情处理完毕
flag = false;
}, 1000);
};
业务场景中处理的技巧2:按钮重置为灰色,移除事件绑定
function handle() {
submit.onclick = null;
submit.disabled = true;
// ...
console.log('OK');
setTimeout(() => {
submit.onclick = handle;
submit.disabled = false;
}, 1000);
}
submit.onclick = handle;
封装防抖函数
/*
debounce:防抖
func:高频出发时的回调函数,必须是函数
wait :高频触发条件,数值类型,单位ms,默认500ms
immediate,是否立即触发,布尔类型,
*/
function debounce(func, wait, immediate) {
// 多个参数及传递默认的处理
if (typeof func !== "function") throw new TypeError("func must be an function!");
if (typeof wait === "undefined") wait = 500;
if (typeof wait === "boolean") {//第二个参数是布尔值,则默认500ms为高频触发条件
immediate = wait;
wait = 500;
}
if (typeof immediate !== "boolean") immediate = false;// 第三个参数不是布尔值,则immediate 默认false
// 设定定时器返回值标识
let timer = null
/* 定时器目的:检测500ms内是否触发第二次,如果有则为高频触发
* 第一次proxy执行,设置一个定时器
* 4ms之后再次点击
* 第二次proxy执行,清除之前设定的定时器,从新设定再去检测500ms内是否有第二次触发
* ...
* 疯狂点击N多次之后,之前的N-1次定时器都清除了,只留最后一次,等过了500ms之后,发现没有点击了,函数执行。
*/
return function proxy(...params) {
let self = this,
now = immediate && !timer;//immediate为true且没有定时器才构成立即触发func函数的条件
clearTimeout(timer); //每一次点击,清除之前的定时器,重新赋值新的定时器
timer = setTimeout(function () {
timer = null;
!immediate ? func.call(self, ...params) : null;//immediate为false,最后一次触发func函数
}, wait);
// 第一次触发就立即执行
//immediate为true,且没有定时器时,才会立即执行传进来的func函数,否则什么都不做。
now ? func.call(self, ...params) : null;
};
}
function handle(ev) {
// 具体在点击的时候要处理的业务
console.log('OK', this, ev);
}
//submit.onclick = debounce(handle,true);
submit.onclick = debounce(handle,500,true);
节流(throttle)
场景:页面滚动触发事件
window.onscroll = function () {
// 默认情况下,页面滚动中:浏览器在最快的反应时间内(4~6MS),就会识别监听一次事件触发,把绑定的方法执行,这样导致方法执行的次数过多,造成不必要的资源浪费
console.log('OK');
};
手写节流函数
/*
throttle:节流
func:高频出发时的回调函数,必须是函数
wait :高频触发条件,数值类型,单位ms,默认500ms
*/
function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError("func must be an function!");
if (typeof wait === "undefined") wait = 500;
let timer = null,
previous = 0; //记录上一次操作的时间
return function proxy(...params) {
let self = this,
now = new Date(), //当前这次触发操作的时间
remaining = wait - (now - previous);// 两次时间间隔
if (remaining <= 0) {
// 两次触发的时间间隔超过wait了,直接执行即可
clearTimeout(timer);
timer = null;
//当事件触发时,清空定时器,防止两个条件同时满足,事件执行两次
previous = now;//事件执行后,重新赋值“上一次”时间
func.call(self, ...params);
} else if (!timer) {
// 两次触发的时间间隔没有超过wait,且没有设置过定时器,则设置定时器,让其等待remaining这么久之后执行一次
timer = setTimeout(function () {
clearTimeout(timer);
timer = null;
previous = new Date();//事件执行后,重新赋值“上一次”时间
func.call(self, ...params);
}, remaining);
}
};
}
fucntion handle(){
console.log("OK~");
};
window.onscroll = throttle(handle);
事件的节流图例: