15.节流

104 阅读3分钟

实现

function throttle(func, wait, immediate) {
  // 多个参数及传递和不传递的默认处理
  if (typeof func !== 'function') throw new TypeError('func must be a function');
  if (typeof wait === 'undefined') wait = 500;
  if (typeof wait === 'boolean') {
    immediate = wait
    wait = 500
  }
  if (typeof immediate !== 'boolean') immediate = false;
  
  // 是否需要立即执行首次
  // 是:可不必定时器,去触发延迟执行
  if (immediate) {
    // 方式一:无需定时器函数
    previous = 0; // 记录上一次操作的时间
    return function proxy(...params) {
      let self = this,
        now = +new Date();// 当前这次触发操作的时间
      // 判断是否需要执行
      if (
        previous === 0 // 第一次执行(上次执行时间为默认值0)
        || now >= previous + wait  // 或超过限制时间节点(当前时间>=上次执行时间+延迟时间)时
      ) {
        previous = now
        func.call(self, ...params)
      }
    }
  }
  // 否
  else {
    let timer = null
    return function proxy(...params) {
      // 是否设置过定时器函数
      // 是:退出
      if (timer) return;
      // 否:没有设置过,则立即执行,且设置定时器标识截断
      timer = setTimeout(function () {
        func.call(self, ...params)
        timer = null // 把定时器标识重置为null
      }, wait)
    }
  }
}
function handle() {
  console.log('ok')
}
document.body.style.height = '5000px'
window.onscroll = throttle(handle, true)

分析过程

function throttle(func, wait, immediate) {
  if (typeof func !== 'function') throw new TypeError('func must be a function');
  if (typeof wait === 'undefined') wait = 500;
  if (typeof wait === 'boolean') {
    immediate = wait
    wait = 500
  }
  if (typeof immediate !== 'boolean') immediate = false;

  // 是否需要立即执行首次
  // 是:可不必定时器,去触发延迟执行
  if (immediate) {
    // 方式一:无需定时器函数
    previous = 0; // 记录上一次操作的时间
    return function proxy(...params) {
      let self = this,
        now = +new Date();// 当前这次触发操作的时间
      // 判断是否需要执行
      if (
        previous === 0 // 第一次执行(上次执行时间为默认值0)
        || now >= previous + wait  // 或超过限制时间节点(当前时间>=上次执行时间+延迟时间)时
      ) {
        previous = now
        func.call(self, ...params)
      }
    }

    // // 方式二:定时器函数-lodash
    // 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(() => {
    //       clearTimeout(timer);
    //       timer = null;
    //       previous = new Date()
    //       func.call(self, ...params)
    //     }, remaining)
    //   }
    // }
  }
  // 否
  else {
    let timer = null
    return function proxy(...params) {
      // 是否设置过定时器函数
      // 是:退出
      if (timer) return;
      // 否:没有设置过,则立即执行,且设置定时器标识截断
      timer = setTimeout(function () {
        func.call(self, ...params)
        timer = null // 把定时器标识重置为null
      }, wait)
    }
  }
}
function handle() {
  console.log('ok')
}
document.body.style.height = '5000px'
window.onscroll = throttle(handle, true)
// window.onscroll = proxy;

// *待解决:借鉴:采用防抖立即执行;改造:加入一次执行
/**
 * 1.网络经典valid,不会立即执行
 * 2.不用setTimeout的实现方案
 * 3.加入immediate控制参数
 * 4.综合整理
 */

function throttle(func, wait) {
  if (typeof func !== 'function') throw new TypeError('func must be a function');
  if (typeof wait === 'undefined') wait = 500;

  let timmer = null,
    previous = 0; // 记录上一次操作的时间
  return function proxy(...params) {
    let self = this,
      now = new Date(),// 当前这次触发操作的时间
      remaining = wait - (now - previous)
    if (remain <= 0) {
      // 两次间隔时间,都超过wait了,直接执行即可
      cleartTimeout(timer); // 兼容临界点
      timer = null;
      previous = now
      func.call(self, ...params)
    } else if (!timer) {
      // 两次触发的间隔时间没有超哥wait,则设置定时器,让其等待remaining这么久之后执行一次「前提:没有设置过定时器」
      timer = setTimeout(() => {
        cleartTimeout(timer);
        timer = null;
        previous = new Date()
        func.call(self, ...params)
      }, remaining)
    }
  }


  // let timmer = null
  // return function proxy(...params) {
  //   if (timer) return;
  //   // 否:没有设置过,则立即执行,且设置定时器标识截断
  //   func.call(self, ...params)
  //   timer = setTimeout(function () {
  //     timer = null // 把定时器标识重置为null
  //   }, wait)
  // }
}
function handle() {
  console.log('ok')
}
window.onscroll = throttle(handle)
// window.onscroll = proxy;

/**
 * 定时器
 * timer = setTimeout(...)
 *  + 给timer赋值,timer是一个数字,存储当前是第几个定时器
 *  + cleartTimeout(timer),从系统中清除定时器,系统中没有定时器,但是timer变量的值还是那个数字呢
 *  + 最好timer重新赋值null
 * 这样就可以基于timer是否为null了解到是否还有定时器了
 *
 * 排号 -》 纸条101 -》 办理业务 -》 系统中清除号码纸条还在你手里(撕掉它)
 *
 * 临界点:过了200ms定时器的时间到了,但是我们也触发了一次
 *  让其执行一次,可以走理解执行,可以清除
 */