函数(二)防抖&节流

节流(throttle):是将函数多次调用变成每隔一段时间执行(比如在onresize时调用的函数)。

防抖(debounce):用来解决一些函数多次调用的问题(例如:一个实时搜索功能),需要加以限制。

延迟执行的防抖函数

只在最后一次触发时,执行目标函数。

const debounce = (func, wait = 500) => {
  let timer = 0
  return function(...args) {

    // 如果500ms再次触发,那么就将定时器重置

    if (timer) clearTimeout(timer);

    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

如果是发送请求的按钮。那么应该使用立即执行的防抖函数

lodash带有立即执行选项的防抖函数:

function debounce (func, wait = 500, immediate = true) {
  let timer, context, args

  // 延迟执行函数
  const later = () => setTimeout(() => {
    // 内存清理
    timer = null

    if (!immediate) {
      // 后执行防抖
      // 使用到之前缓存的参数和上下文
      func.apply(context, args)

      // 清理内存
      context = args = null
    }
  }, wait)

  // 这里返回的函数是每次实际调用的函数
  return function(...params) {
    if (!timer) {
      // 如果没有setTimeout,就创建一个
      timer = later()

      if (immediate) {
        // 立即执行的防抖函数
        func.apply(this, params)
      } else {
        // 后执行的防抖函数
        // 缓存参数和调用上下文
        context = this
        args = params
      }

    } else {
      // 如果有setTimeout,那么重新计时
      clearTimeout(timer)
      timer = later()
    }
  }
}

节流函数

比较缓存时间和当前时间是否在时间间隔外

underscore的节流函数源码实现

_.throttle = function(func, wait, options) {

  // options选项
  // 如果想忽略开始函数的的调用,传入{leading: false}
  // 如果想忽略结尾函数的调用,传入{trailing: false}

  var context, args, result;
  var timeout = null;

  // 缓存上次调用时间
  var previous = 0;

  // 如果 options 没传则设为空对象
  if (!options) options = {};

  // 定时器回调函数
  var later = function() {

    // 如果设置了 leading(忽略开始函数),就将 previous 设为 0
    previous = options.leading === false ? 0 : _.now();

    // 置空一是为了防止内存泄漏,二是为了下面的定时器判断
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;

  };

  return function() {

    // 获得当前时间戳
    var now = _.now();

    // 首次进入:因为previous=0,所以,如果不设置忽略开始函数
    // 那么 previous = now
    if (!previous && options.leading === false) previous = now;

    // 计算剩余时间
    var remaining = wait - (now - previous);

    context = this;
    args = arguments;

    if (remaining <= 0 || remaining > wait) {
      // 如果当前调用已经大于上次调用时间+wait。(已过时间间隔)
      // 或者用户手动调了时间

      // 如果存在定时器就清理掉
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }

      // 缓存时间
      previous = now;
      result = func.apply(context, args);

      // 清理内存
      if (!timeout) context = args = null;

    } else if (!timeout && options.trailing !== false) {
      // 开始或者结尾时调用的函数

      // 开始调用时: remaining = wait
      // 最后调用时:remaining = 距离wait时间
      timeout = setTimeout(later, remaining); 
    }

    return result;
  };
};

Last updated