防抖大致意思是高频触发的函数,在一定的时间间隔内只会执行一次。主要用来处理窗口的resize,滑动,滚动,输入等高频事件,也可以处理防止恶意高频点击按钮。
简易版防抖
const d = function(fn, wait){
let timeout = null
return function(){
clearTimeout(timeout)
let args = arguments
timeout = setTimeout(() => {
fn(...args)
}, wait);
}
}
实际工作中很少会手写这些工具代码,一般都会使用lodash或者underscore这些工具库。分析下underscore的防抖代码使用定时器结合时间戳和闭包实现的非常巧妙
underscore版防抖
const _debounce = function (func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function () {
var last = new Date().valueOf() - timestamp;
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function () {
context = this;
args = arguments;
timestamp = new Date().valueOf();
var callNow = immediate && !timeout;
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
console.log(result, 22);
return result;
};
};
// JS解释器遇到_debounce(func, 4000, false) 会将func做防抖处理生成一个新的函数设为d_func, 并将_debounce内定义的变量(timeout, args, context, timestamp, result, later)以闭包的形式留存,供d_func使用。
// 事件触发时实际情况是: window.addEventListener('resize', d_func })
// d_func函数执行流程分析
// 首次触发:(timeout为null)
// context = this; 保存this指向到context,此处this指向事件的调用者节点boxNode(<div class="box"></div>)
// args = arguments; 保存匿名函数func的入参到args
// timestamp = new Date().valueOf(); 记录此次触发的时间戳
// var callNow = immediate && !timeout; 创建标识 callNow 条件是 immediate为true, 并且不存在timeout(不存在即是首次触发)
// immediate为false的话 只会执行定时器的任务
// if (!timeout) timeout = setTimeout(later, wait); 创建定时器
// callNow为真时执行 回调函数 只有immediate为真时并且首次触发时才会执行
// result = func.apply(context, args); 绑定this,传入参数
// context = args = null; 释放内存
// 时间间隔内再次触发:
// 依旧是保存this
// 保存arguments
// 刷新触发的时间戳timestamp
// 判断timeout是否存在 (此时timeout肯定存在)
// 此时这个匿名函数func内只负责刷新 timestamp, 等待定时器执行
// 定时器执行时 调用later函数
// later函数执行
// 获取当前时间和触发时间timestamp的间隔last 可以叫做触发间隔
// last小于_debounce设置的间隔wait 重新设置timeout setTimeout(later, wait - last); 并把间隔设置为剩余时间间隔
// last >= wait 执行 释放timeout=null immediate为false时,执行func.apply(context, args); 释放 context = args = null。
// 以timeout变量为标识,分辨是首次触发还是,触发间隔wait内的重复触发。避免了常规写法的多次设置/清除定时器。
假设要处理window的resize事件防抖:
const func = (a) => {
console.log("窗口尺寸变化了......");
};
window.addEventListener('resize', _debounce(func, 500))
如果想往func传参数a, 可以
const d_func = _debounce(func, 500)
window.addEventListener('resize', ()=>{d_func(‘我是传入的参数’)})