例如 scroll、input 等事件频繁触发时,可能造成性能问题,若事件处理时使用了异步方法(例如 ajax 请求),还有可能导致结果错误,例如输入框输入时自动 ajax 请求查询数据,若不进行处理,则会频繁触发 ajax 请求,且显示的查询结果可能不是当前输入内容的返回结果。
概念
- 设定一个周期,期间不执行动作
 - 前缘 leading 和延迟 trailing
- 前缘:执行动作后再开始周期
 - 延迟:周期结束后再执行动作
 
 - 防抖 debounce 和节流 throttle
- 防抖:若期间动作又被触发,则重新设定周期
 - 节流:周期固定
 
 
应用场景
防抖 debounce
简单来说,就是在一段时间大量频繁触发事件时,只在最后一次事件触发时执行动作。
- 比如输入框 input onchange,输入查询字符串时实时搜索相应结果,只需要查询最后输入完成的字符串。
 - 又或者窗口 resize 后重新计算页面某一元素尺寸 width/height,只需要 resize 完成后进行计算。
节流 throttle
而有些情况下则需要限制频率的同时多次触发,而非只是最后一次。
例如最常见的监听 scroll 事件并动态加载某一元素(例如返回顶部按钮,或是懒加载图片等等) 
lodash 实现
防抖 debounce
/*** 部分代码有一定简化* fn: 执行函数* wait: 周期时间* leading: true 表示前缘执行,false 表示延迟执行* maxWait: 如果设置了,就表示固定 maxWait 时间后一定执行*/function debounce(fn, wait, leading = false, maxWait) {let result;let lastThis;let lastCallTime;let lastInvokeTime = 0;let timerId;if (maxWait !== undefined) maxWait = Math.max(maxWait || 0, wait);// 开始周期function leadingEdge(time) {lastInvokeTime = time;timerId = setTimeout(timerExpired, wait);// 若设置了前缘 leading,则立即执行一次 fnreturn leading ? invokeFunc(time) : result;}// 结束周期function timeExpired() {const time = Date.now();if (shouldInvoke(time)) {timerId = undefined;if (!leading && lastArgs) {// lastArgs 表示 fn 至少 debounced 了一次return invokeFunc(time);}lastArgs = lastThis = undefined;}return result;}// 执行动作function invokeFunc(time) {const args = lastArgs;const thisArg = lastThis;lastArgs = lastThis = undefined;lastInvokeTime = time;result = fn.call(thisArg, ...args);return result;}// 判断是否处于可执行阶段function shouldInvoke(time) {const timeSinceLastCall = time - lastCallTime;const timeSinceLastInvoke = time - lastInvokeTime;return (lastCallTime === undefined // 第一次调用|| timeSinceLastCall >= wait // 已经过了一个周期不需要等待了|| timeSinceLastCall < 0 // shouldInvoke 调用已经过时了|| (maxWait && timeSinceLastInvoke >= maxWait) // 到了固定周期执行时间了);}function debounced(...args) {const time = Date.now();lastThis = this;lastArgs = args;lastCallTime = time;if (shouldInvoke(time)) {if (timerId === undefined) {// 没有等待的周期return leadingEdge(lastCallTime);}if (maxWait) {// 有设置固定强制执行周期时间时,强制调用timerId = setTimeout(timeExpired, wait);return invokeFunc(lastCallTime);}}if (timerId === undefined) {// 第一次调用timerId = setTimeout(timeExpired, wait);}return result;}return debounced;}
节流 throttle
function throttle(fn, wait, leading) {// throttle 和 debounce 的差别在于固定周期执行,即 maxWait 设置为 waitreturn debounce(fn, wait, leading, wait);}
