文章

前端面試手寫練習 - throttle (進階)

文章發表於

問題

在先前的版本中,似乎少了一些使用上彈性以及功能,想像一下,如果今天是實作一個無限滾動的功能,每當使用者滾動到底部時才會載入更多的資料,但聰明的你發現不需要每次滑動時都去呼叫載入更多,這毫無意義。

這時候剛好想上次有實作過的 throttle,所以就開心地將它拿來使用,所以行為看起來會是以下範例:

<!DOCTYPE html>
<html>

<head>
  <title>Parcel Sandbox</title>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/styles.css" />
</head>

<body>
  <h1>Hello world</h1>
</body>

</html>

不知道各位讀者有沒有發現問題?如果將 loadMoreContent 用先前的 throttle 包起來,會發現每次滾動到底部時就沒反應了,原因是最後一次滑動事件被忽略了。

主要是先前的 throttle 只會確保不是冷卻時間時才會執行,這代表它可以確保第一次 (稱作 leading) 滾動時會執行 loadMoreContent,但當滾動到底部時,throttle 可能因為還在冷卻時間,而被忽略掉了。

所以本篇文章就是要實作一個更進階的 throttle,讓可以讓開發者自行決定是否要在 leading 與 trailing 時執行。

function throttle(
func: Function,
wait: number,
options: { leading?: boolean; trailing?: boolean }
): Function

練習區

在了解問題後,可以嘗試先寫下您的思路,再到下方的練習區域實際寫出程式碼。

import { add } from './add';

describe('add', () => {
  test('Commutative Law of Addition', () => {
    expect(add(1, 2)).toBe(add(2, 1));
  });
});

Open browser consoleTests

實作完成後,可以將您的程式碼複製到上面的範例中,理論上在滑動到底部時就可以正常載入更多資料了。

筆者思路

  1. 我們可以先實作一個 leadingtrailing 都為 true 的版本。
  2. 只需基於原本的 throttle 函式,加上 savedArgssavedThis 來儲存最後一次的參數與 this
  3. 並在 setTimeout 結束時,先執行一次 func,同時檢查是否有 savedArgs,如果有的話就將 savedArgssavedThis 設為 null,並在冷卻時間結束後執行一次 func
  4. 最後再加上 options 的判斷,如果 leadingtrailingfalse,則不執行 func

筆者解答

function throttle(func, wait, options = { leading: true, trailing: true }) {
let isThrottled = false
let savedArgs
let savedThis
const later = () => {
if (savedArgs && options.trailing) {
func.apply(savedThis, savedArgs)
savedThis = savedArgs = null
setTimeout(later, wait)
} else {
isThrottled = false
}
}
return function (...args) {
if (isThrottled) {
savedThis = this
savedArgs = args
return
}
isThrottled = true
setTimeout(later, wait)
if (options.leading) {
func.apply(this, args)
}
}
}

相關題目

  1. bigfrontend.dev - throttle with leading & trailing option

延伸閱讀

  1. Debounce vs Throttle: Definitive Visual Guide
如果您喜歡這篇文章,請點擊下方按鈕分享給更多人,這將是對筆者創作的最大支持和鼓勵。