この解答例はChatGPTによって生成されています,正しいかは自己判断で。

BFE.devでのAI解答例 - JavaScript コーディング問題
7. leading & trailingをサポートするdebounce()を実装する

function debounce(func, wait, option = { leading: false, trailing: true }) {
  let timeoutId;
  let lastArgs;
  let lastThis;
  let maxWait;
  let result;
  let lastCallTime = 0;
  let lastInvokeTime = 0;

  const leadingCall = () => {
    lastInvokeTime = Date.now();
    timeoutId = setTimeout(timerExpired, wait);
    return option.leading ? invokeFunc(lastArgs, lastThis) : result;
  };

  const timerExpired = () => {
    const timeSinceLastCall = Date.now() - lastCallTime;
    const timeSinceLastInvoke = Date.now() - lastInvokeTime;

    if (timeSinceLastCall < wait && timeSinceLastCall >= 0) {
      timeoutId = setTimeout(timerExpired, wait - timeSinceLastCall);
    } else {
      timeoutId = null;
      if (!option.leading) {
        result = invokeFunc(lastArgs, lastThis);
      }
      if (!timeoutId && maxWait !== wait) {
        result = invokeFunc(lastArgs, lastThis);
      }
    }
  };

  const invokeFunc = (args, context) => {
    lastInvokeTime = Date.now();
    lastThis = context;
    lastArgs = args;
    lastCallTime = lastInvokeTime;
    return func.apply(context, args);
  };

  const leadingTimerExpired = () => {
    timeoutId = null;
    result = invokeFunc(lastArgs, lastThis);
  };

  const debounced = (...args) => {
    lastArgs = args;
    const time = Date.now();
    lastThis = this;

    if (!lastInvokeTime && option.leading === false) {
      lastInvokeTime = time;
    }

    const timeSinceLastCall = time - lastCallTime;
    if (timeSinceLastCall >= wait || timeSinceLastCall < 0) {
      if (timeoutId) {
        clearTimeout(timeoutId);
        timeoutId = null;
      }
      lastCallTime = time;
      result = invokeFunc(args, this);
    } else if (!timeoutId && option.trailing !== false) {
      timeoutId = setTimeout(
        option.leading === false ? leadingTimerExpired : timerExpired,
        wait - timeSinceLastCall
      );
    }

    if (option.maxWait !== undefined) {
      maxWait = Number(option.maxWait);
      const timeSinceLastInvoke = time - lastInvokeTime;
      if (timeSinceLastInvoke >= maxWait || timeSinceLastInvoke < 0) {
        invokeFunc(args, this);
      } else if (!timeoutId) {
        timeoutId = setTimeout(timerExpired, maxWait - timeSinceLastInvoke);
      }
    }

    return result;
  };

  debounced.cancel = () => {
    clearTimeout(timeoutId);
    lastInvokeTime = 0;
    lastArgs = null;
    lastThis = null;
    lastCallTime = 0;
    timeoutId = null;
  };

  debounced.flush = () => {
    if (timeoutId) {
      clearTimeout(timeoutId);
      timerExpired();
    }
  };

  return debounced;
}

解説:

lodash.debounce()ではない手作りのdebounce()を実装する続きです。新しい変数と分岐がたくさんあるので、注意深く読んで実装を見てください。

Code Breakdown:

  1. 関数debounce()を宣言し、以下の引数を受け取ります:
    1. func (Function): 処理したい関数。
    2. wait (Number): 待機する時間(ミリ秒)。
    3. option.leading (Boolean): funcの1回目を呼び出すかどうか。デフォルトは false
    4. option.trailing (Boolean): 待機タイマーの後にfuncを呼び出すかどうか。デフォルトは true
  2. timeoutId変数を宣言し、nullに設定します。
  3. lastArgs変数を宣言し、空配列に設定します。これは、実行される関数に渡される引数を格納します。
  4. lastThis変数を宣言し、nullに設定します。これは、実行される関数のコンテキストを格納します。
  5. maxWait変数を宣言し、nullに設定します。
  6. result変数を宣言し、undefinedに設定します。
  7. lastCallTime変数を宣言し、0に設定します。
  8. lastInvokeTime変数を宣言し、0に設定します。
  9. leadingCall()関数を宣言し、以下の手順で行います。
    1. Date.now()を呼び出し、現在時刻を取得します。これはlastInvokeTimeに保存されます。
    2. setTimeout() を使用して、 waitミリ秒後に timerExpired()関数を呼び出します。これは timeoutId に保存されます。
    3. もし option.leadingtrue なら、invokeFunc() を呼び出して、 result を返します。 そうでなければ resultundefinedのままです。
  10. timerExpired()関数を宣言します。
    1. Date.now()を呼び出して、lastCallTimeから現在時刻までの経過時間を取得します。
    2. Date.now()を呼び出して、lastInvokeTimeから現在時刻までの経過時間を取得します。
    3. もし wait - timeSinceLastCallが0以上である場合で、timeoutIdがnullである場合は、 setTimeout()を使って wait - timeSinceLastCall時間後に timerExpired()関数を呼び出します。
    4. timeoutIdがnullである場合、 option.leadingfalseではない場合は、 resultinvokeFunc()から呼び出します。
    5. !timeoutIdかつmaxWaitwaitと異なる場合、 resultinvokeFunc(lastArgs, lastThis)から呼び出します。
  11. invokeFunc(args, context)関数を宣言します。
    1. Date.now()を呼び出して、現在時刻を取得します。これは、lastInvokeTimeに保存されます。
    2. lastThiscontextをセットします。
    3. lastArgsargsをセットします。
    4. lastCallTimelastInvokeTimeに設定し、それによってこれらの変数が一致するようにします。
    5. contextと同じthisfuncを実行して、その結果を返します。
  12. leadingTimerExpired()関数を宣言し、 resultinvokeFunc()から呼び出し、 timeoutIdをnullに設定します。
  13. lastCallTimelastArgslastThislastInvokeTimetimeoutIdの値を最初の状態にリセットするcancel()関数を宣言します。
  14. timeoutIdがある場合、 timerExpired()関数を直ちに呼び出して、 resultを返すflush()関数を宣言します。
  15. debounced関数を宣言し、以下の手順で行います。
    1. argslastArgsに設定しましょう。
    2. Date.now()を呼び出して現在時刻を取得します。これは time に保存されます。
    3. thislastThisに設定します。
    4. option.leadingfalselastInvokeTimeが nullである場合は、 lastInvokeTimetimeを保存します。
    5. timeSinceLastCallを計算し、 waitよりも大きいまたは0未満である場合は、以下の順番で実行します。
      1. timeoutIdがある場合(前回のタイムアウトがまだ働いている場合)、clearTimeout()関数を呼び出してタイムアウトをキャンセルし、 timeoutIdをnullに設定します。
      2. lastCallTimetimeを設定します。
      3. resultinvokeFunc(args, this)を設定します。
    6. timeoutIdがnullでかつoption.trailingfalseではない場合、以下の手順で実行してください。
      1. setTimeout()を使用して、 wait - timeSinceLastCall時間後にleadingTimerExpired()timerExpired()を呼び出します。前者はoption.leadingfalseのときで、後者はoption.leadingfalseでない場合です。それらのうち、最初の一回だけが実行されます。
      2. timeoutIdを設定します。
    7. option.maxWaitが存在する場合は、以下の手順で実行してください。
      1. maxWaitoption.maxWaitNumber()関数を使って変換します。
      2. timeSinceLastInvokeを計算し、 maxWait以上であるか、または0未満である場合、 invokeFunc(args, this)を呼び出しします。
      3. timeoutIdがnullである場合は、以下の手順で実行してください。
        1. setTimeout()を使用して、 maxWait -timeSinceLastInvoke時間後に timerExpired()を呼び出します。
        2. timeoutIdを設定します。
    8. resultを返します。
  16. cancel()flush()メソッドを、それぞれキャンセルと即座に呼び出すように debounced関数に追加します。
  17. debounced関数を返します。