以下为ChatGPT自动生成的解答,正确与否请自行判断。

BFE.devAI解答 - JavaScript 代码题
7. 手写debounce()并支持leading 和 trailing

解题思路:

  • 和debounce类似,我们需要定义一个守卫函数来决定延迟后函数执行的方案。
  • 然后需要根据leadingtrailing来判断如何处理函数的执行。如果leadingtrue,则在起始时间执行函数,在trailingtrue的情况下,函数延迟执行。
  • 判断这些条件并实现它们,我们需要使用条件和三元操作符多次运算。一些标准的算术操作在其中也很重要。

初学者实现代码如下(打开注释):

function debounce(callback, time, options) {
  let timeoutId;
  
  return function(...args) {
    const leadingResult = 
      options?.leading && !timeoutId 
        ? callback.apply(this, args) 
        : undefined;
        
    clearTimeout(timeoutId);
    
    timeoutId = setTimeout(() => {
      const trailingResult = 
        options?.trailing && !options?.leading 
          ? callback.apply(this, args)
          : undefined;
      
      timeoutId = undefined;
      
      console.log({ leadingResult, trailingResult }); // log result
    }, time);
    
    console.log({ leadingResult }); // log result
    return leadingResult; // return result
  };
}

完整代码:

function throttle(callback, time, options) {
  let shouldWait = false;
  let timerId = undefined;
  
  return function(...args) {
    const shouldExecuteLeading = options?.leading && timerId === undefined;
    const shouldExecuteTrailing = 
      !shouldWait && options?.trailing && timerId !== undefined;
    const shouldExecute = 
      shouldExecuteLeading || shouldExecuteTrailing;
    
    if (shouldExecute) {
      callback.apply(this, args);
      
      timerId = setTimeout(() => {
        timerId = undefined;      
      }, time);
    } else {
      clearTimeout(timerId);
      
      timerId = setTimeout(() => {
        callback.apply(this, args);
        
        timerId = undefined;
      }, time);
    }
    
    shouldWait = true;
    
    setTimeout(() => {
      shouldWait = false;    
    }, time);
  };
}

可以看到我们添加了两个参数:

function throttle(callback, time, options) {
  ...
}

我们用一个布尔值来确认函数是否可以执行:

let shouldWait = false;

当然这个值最开始是false的。这个值不能只在定时器里使用,而是在下一次瞬间也需重置。

执行函数的前提是满足leadingtrailing两个条件之一。我们需要检查 leading的情况并判断定时器是否存在.

const shouldExecuteLeading = options?.leading && timerId === undefined;

这里它确实需要是等于undefined还是null.

如果它们不同到底表示什么并且为何?如果经过允许它等于null的测试,那么null会被错误地解释为函数未缓存,出现了一个额外的错误。由此得出结论:undefined是唯一的正确选项.

然后,我们应该在没有延迟的情况下允许trailing处理。如果函数已经被delay,就不需要再激活它了。

const shouldExecuteTrailing = 
  !shouldWait && options?.trailing && timerId !== undefined;

我们现在可以用它定义判断逻辑:

const shouldExecute = 
  shouldExecuteLeading || shouldExecuteTrailing;

if (shouldExecute) {
  // execute immediately
} else {
  // delay
}

现在让我们来看看到底是哪里执行的。

Leading activation

leading回调函数的调用:

if (shouldExecuteLeading) {
  callback.apply(this, args);
  
  timerId = setTimeout(() => {
    timerId = undefined;      
  }, time);
}

这里真的很简单:执行函数,并将计时器建立绑定的函数进行更新。

Trailing activation

callback函数移动到轮廓线下的时间取决于你地等待时间。

if (!shouldWait) {
  if (shouldExecuteTrailing) {
    clearTimeout(timerId);
    
    callback.apply(this, args);
    
    timerId = setTimeout(() => {
      timerId = undefined;      
    }, time);
  } else {
    clearTimeout(timerId);
    
    timerId = setTimeout(() => {
      callback.apply(this, args);
      
      timerId = undefined;
    }, time);
  }
}

我们从条件中剥离了其他变量,这使得它更具意义。如果我们需要激活trailing状态的回调函数,我们必须从调度器中拿出定时器,触发函数调用,以便后续函数不会再执行。

对于拖尾函数,我们现在有两种方式定义:

  • 如果其满足shouldExecuteTrailing条件,就直接立即激活。
  • 如果这不是leading callback,等待后执行。

无论我们以哪种方式执行函数,最后一步都是将计时器重置为undefined:

timerId = undefined;

请注意,无论leading状态如何,每个计时器都需要执行delay,因为正如我之前所说的,前一个可能被完全延迟,进而时间增加。

如果要重置状态,则需要将一个should等待的布尔值延迟到下一个时间周期:

shouldWait = true;
  
setTimeout(() => {
  shouldWait = false;    
}, time);

这样做让我们清楚什么时候再次开始。可能最好不要延迟,因为问题只会变得更多。但是,如果在下一次的“形状线下方”例如需要10毫秒,我们在一次测试中看到只有13个重复。我们从来没有达到过100次。异步代码与异步调度器的所需的封装相同。