跳至主要內容

防抖与节流

yyshino大约 2 分钟

防抖

防抖:动作绑定事件,动作发生后一定时间后触发事件,在这段时间内,如果该动作又发生,则重新等待一定时间再触发事件。

几种实现


function debounce(func,time){
	let timer = null;
	return () => {
		clearTimerout(timer); // 事件再次触发,清除定时器 
		timer = setTimeout(() => { // 等待time时间后,再触发事件
			func.apply(this,arguments);
		},time);
	}
}


/**
 * 快速书写一个防抖函数
 * @description 只要一直调用, callback 将不会被触发
 * 在一次调用结束后, 只有等待 timeout ms 时间, 才能继续调用 callback
 * immediate 决定触发时机
 * @example 
 * 1. 点击按钮发送请求(保存数据之类的)
 * 2. 搜索时自动联想
 * 3. 自动保存
 * 4. Debouncing a resize/scroll event handler
 */
function debounce(callback, timeout, immediate) {
  let timer;
  return function () {
    const context = this; // 持有执行上下文
    const args = arguments; // 记录传参
    const later = function () {
      timer = null; // 贤者时间过了,重振旗鼓,重置为初始状态
      if (!immediate) callback.apply(context, args); // 设置为尾部调用才延时触发
    }
    const callNow = immediate && !timer; // 如果确认允许首部调用,且首次调用,那么本次立即触发
    clearTimeout(timer); // 杀掉上次的计时器,重新计时
    timer = setTimeout(later, timeout); // 重启一个计时器,过了贤者时间之后才触发
    callNow && callback.apply(context, args); // 设置为首部调用立即触发
  }
}

节流

节流: 动作绑定事件,动作发生后一段时间后触发事件,在这段时间内,如果动作又发生,则无视该动作,直到事件执行完后,才能重新触发。

几种实现

function throtte(func,time){
	let activeTime = 0;
	return () => {
		const current = Date.now();
		if(current - activeTime > time){ // 在time时间内,如果再次触发则无视
			func.apply(this,arguments);
			activeTime = Date.now();
		}
	}
}


/**
 *  快速书写一个节流函数
 * @description 一直调用 callback, 每隔 timeout ms 时间 callback 触发一次
 * 在 timeout ms 时间内的调用将不触发
 * @example
 * 1. Throttling a button click so we can’t spam click 控制疯狂按钮的响应频率
 * 2. Throttling an API call 控制 API 的调用频率
 * 3. Throttling a mousemove/touchmove event handler 控制频繁触发事件的相应频率
 */
function throttle(callback, timeout){
     let triggerTime; // 记录每次真正触发时间
     return function(){
         const context = this; // 持有执行上下文
         const args = arguments; // 记录传参
         if(triggerTime === undefined // 首次调用
            || Date.now() - triggerTime > timeout){ // 贤者时间已经过去
             triggerTime = Date.now(); // 记录真正触发时间
             callback.apply(context, args); // 可以触发回调
         }
     }
}

// solution2 间隔时间反转标志位
function throttle(callback,timeout){
    let disable; // 触发回调是否禁用
    return function(){
        const context = this; // 持有执行上下文
        const args = arguments; // 记录传参
        if(!disable) { // 首次调用或者贤者时间过了,禁用解除
            callback.apply(context,args); // 可以触发回调
            disable = true; // 马上禁用
            setTimeout(_ => disable = false , timeout); // 贤者时间过了,禁用解除
        }
    }
}