解析underscore中的debounce

先奉上源码

取自Underscore.js 1.9.1的debounce

_.debounce = function(func, wait, immediate) {
  var timeout, result;

  var later = function(context, args) {
    timeout = null;
    if (args) result = func.apply(context, args);
  };

  var debounced = restArguments(function(args) {
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      var callNow = !timeout;
      timeout = setTimeout(later, wait);
      if (callNow) result = func.apply(this, args);
    } else {
      timeout = _.delay(later, wait, this, args);
    }

    return result;
  });

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
};

其中比较陌生的是restArguments_.delay,那么我们首先来逐个分析它们

restArguments

// Some functions take a variable number of arguments, or a few expected
// arguments at the beginning and then a variable number of values to operate
// on. This helper accumulates all remaining arguments past the function’s
// argument length (or an explicit `startIndex`), into an array that becomes
// the last argument. Similar to ES6’s "rest parameter".
var restArguments = function(func, startIndex) {
  startIndex = startIndex == null ? func.length - 1 : +startIndex;
  return function() {
    var length = Math.max(arguments.length - startIndex, 0),
        rest = Array(length),
        index = 0;
    for (; index < length; index++) {
      rest[index] = arguments[index + startIndex];
    }

    // 个人觉得这段switch没有特别意义,可以删除
    // switch (startIndex) {
    //  case 0: return func.call(this, rest);
    //  case 1: return func.call(this, arguments[0], rest);
    // case 2: return func.call(this, arguments[0], arguments[1], rest);
    // }

    var args = Array(startIndex + 1);
    for (index = 0; index < startIndex; index++) {
      args[index] = arguments[index];
    }
    args[startIndex] = rest;
    return func.apply(this, args);
  };
};

它很类似ES6剩余参数
举个例子

function sum (a, b, rest) {
  var sum = a + b;
  console.log(Array.isArray(rest)); // 打印true
  if (rest.length) {
    sum += rest.reduce((x, y) => x + y);
  }
  return sum;
}
ra_sum = restArguments(sum);
console.log(ra_sum(1, 2)); // 8
console.log(ra_sum(1, 2, 3, 4, 5)); // 15

// 利用ES6的剩余参数可以这样写
function es6_ra_sum(a, b, ...rest) {
  var sum = a + b;
  console.log(rest)
  console.log(Array.isArray(rest)); // 打印true
  if (rest.length) {
    sum += rest.reduce((x, y) => x + y);
  }
  return sum;
}
console.log(es6_ra_sum(1, 2)); // 3
console.log(es6_ra_sum(1, 2, 3, 4, 5)); // 15

_.delay

// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = restArguments(function(func, wait, args) {
  return setTimeout(function() {
    return func.apply(null, args);
  }, wait);
});

// 相当于
_.delay = function(func, wait, ...args) {
  return setTimeout(function() {
    return func.apply(null, args);
  }, wait);
}

_.debounce

_.debounce = function(func, wait, immediate) {
  var timeout, result;

  var later = function(context, args) {
    timeout = null; // 重置timeout为了leading执行

    // 判断arg是为了下面运行timeout = setTimeout(later, wait);这句话时func不会被执行
    if (args) result = func.apply(context, args);
  };

  // 原本来是restArgumenst返回函数,这里为了直观我直接换成es6的剩余参数形式
  var debounced = function(...args) {
    if (timeout) clearTimeout(timeout);

    if (immediate) {
      //初始的时候timeout为undefined,later函数运行的时候置为null, 这两种情况callNow为true
      var callNow = !timeout;

      // 下面这句话的目的不是为了执行func而是切换timeout的值,也就是间接改变callNow。而且later中args并没有传入所以不会执行later中不会执行func
      timeout = setTimeout(later, wait);

      // 这句话才是当immediate为true时真正地执行func
      if (callNow) result = func.apply(this, args);
    } else {
      // trailing执行func
      timeout = _.delay(later, wait, this, args);
      // 相当于setTimeout(function() {
      //    return later.apply(null, [this, args]);
      // }, wait);
      // 再在later中运行result = func.apply(this, args); 最后和callNow的时候运行一致
    }
    return result;
  }

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
};
posted @ 2018-09-06 13:23  Guanine  阅读(468)  评论(0编辑  收藏  举报