# 1. 防抖和节流概念

防抖:在用户频繁触发函数的某个阶段中, 识别一次(识别第一次/识别最后一次),如果在n秒内又触发了时间,则会重新计算函数执行时间【可以设置频繁的间隔时间】

节流:在用户连续触发事件,但是在n秒内只执行一次函数。只是降低了函数的 执行频率

# 2. 防抖和节流应用场景

# 防抖应用场景

按钮点击:只执行刚开始那次,或最后的点击那次 表单验证: 触发一段连续的输入事件,只取最后的输入结果

# 节流应用场景

拖拽场景:固定事件内只执行一次,防止高频率触发导致位置变动 监听滚动事件: 如果做图片懒加载时,限制滚动条的滚动频率 动画性能: 避免短时间内多次触发动画,引起性能问题

# 3. 防抖的实现过程

# 防抖 执行流程

  • 第一次点击,没有立即执行,等待500ms,看看这个事件内有没有触发第二次
  • 有触发第二次说明在频繁点击,不去执行我们的事情(继续看第二次和第三次间隔... )
  • 如果没有触发第二次,则认为非频繁点击,此时触发;

# 以最后一次触发为标准

/**
 * 实现函数的防抖(目的是频繁触发中只执行一次)
 * @param {*} func 需要执行的函数
 * @param {*} wait 检测防抖的间隔频率
 * @param {*} immediate 是否是立即执行  True:第一次,默认False:最后一次
 * @return {可被调用执行的函数}
 */
function debounce(func, wati = 500, immediate = false) {
  let timer = null
  return function anonymous(... params) {

    clearTimeout(timer)
    timer = setTimeout(_ => {
      // 在下一个500ms 执行func之前,将timer = null
      //(因为clearInterval只能清除定时器,但timer还有值)
      // 为了确保后续每一次执行都和最初结果一样,赋值为null
      // 也可以通过 timer 是否 为 null 是否有定时器
      timer = null
      func.call(this, ...params)
    }, wait)

  }
}

# 以第一次触发为标准

(第一次点击,立即触发,随后立马接着点击,不会触发,除非超过间隔时间,重新触发函数)

/**
 * 实现函数的防抖(目的是频繁触发中只执行一次)
 * @param {*} func 需要执行的函数
 * @param {*} wait 检测防抖的间隔频率
 * @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次
 * @return {可被调用执行的函数}
 */
function debounce(func, wait = 500, immediate = true) {
  let timer = null
  return function anonymous(... params) {

    // 第一点击 没有设置过任何定时器 timer就要为 null
    let now = immediate && !timer
    clearTimeout(timer)
    timer = setTimeout(_ => {
      // 在下一个500ms 执行func之前,将timer = null
      //(因为clearInterval只能在系统内清除定时器,但timer还有值)
      // 为了确保后续每一次执行都和最初结果一样,赋值为null
      // 也可以通过 timer 是否 为 null 是否有定时器
      timer = null!immediate ? func.call(this, ...params) : null
    }, wait)
    now ? func.call(this, ...params) : null

  }
}

function func() {
  console. log('ok')
}
btn. onclick = debounce(func, 500)

# 4. 节流的实现过程

【第一次触发:reamining是负数,previous被赋值为当前时间】

【第二次触发:假设时间间隔是500ms,第一次执行完之后,20ms之后,立即触发第二次,则remaining = 500 - ( 新的当前时间 - 上一次触发时间 ) = 500 - 20 = 480

/**
 * 实现函数的节流 (目的是频繁触发中缩减频率)
 * @param {*} func 需要执行的函数
 * @param {*} wait 检测节流的间隔频率
 * @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次
 * @return {可被调用执行的函数}
 */
function throttle(func, wait) {
	let timer = null
  let previous = 0  // 记录上一次操作的时间点

  return function anonymous(... params) {
    let now = new Date()  // 当前操作的时间点
    remaining = wait - (now - previous) // 剩下的时间
    if (remaining <= 0) {
      // 两次间隔时间超过频率,把方法执行
      
      clearTimeout(timer); // clearTimeout是从系统中清除定时器,但timer值不会变为null
      timer = null; // 后续可以通过判断 timer是否为null,而判断是否有 定时器
      
      // 此时已经执行func 函数,应该将上次触发函数的时间点 = 现在触发的时间点 new Date()
      previous = new Date(); // 把上一次操作时间修改为当前时间
      func.call(this, ...params);
    } else if(!timer){ 
      
      // 两次间隔的事件没有超过频率,说明还没有达到触发标准,设置定时器等待即可(还差多久等多久)
      // 假设事件间隔为500ms,第一次执行完之后,20ms后再次点击执行,则剩余 480ms,就能等待480ms
      timer = setTimeout( _ => {
        clearTimeout(timer)
        timer = null // 确保每次执行完的时候,timer 都清 0,回到初始状态
        
        //过了remaining时间后,才去执行func,所以previous不能等于初始时的 now
        previous = new Date(); // 把上一次操作时间修改为当前时间
        func.call(this, ...params)}, remaining)
    }
  }
}

function func() {
  console. log('ok')
}
btn. onclick = throttle(func, 500)