10 個專案必備的 JavaScript 工具函式
每個開發者都有這種時刻:專案進行到一半,突然要格式化日期、做 debounce、或從 localStorage 解析來路不明的 JSON 又不能搞掛整個 app。於是開新分頁、Google、複製 2017 年的 Stack Overflow 答案、微調、繼續。下個月再來一次。
為了跳出這個迴圈,可以建立自己的「工具帶」——一組小函式,像隨身行李一樣跟著你從專案到專案。不炫技,但實用。以下是 10 個我拒絕在沒有它們的情況下開新專案的函式。
1. Debouncer — 管好觸發過頭的事件
捲動、resize、搜尋輸入都會狂噴事件。用一個小 wrapper 讓它們乖一點。
export function debounce(func, wait = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), wait);
};
}
實際用法:
// 搜尋列不會把 API 打爆
const onSearch = debounce((query) => fetchResults(query), 400);
inputEl.addEventListener("input", (e) => onSearch(e.target.value));
沒 debounce 時,打 "react hooks" 會觸發 11 次 API;有 debounce 只打 1 次。
2. Date Whisperer — toLocaleDateString 選項太多時的救星
日期格式化在 JS 裡是「大家都忘了怎麼解」的已解問題。
export function formatDate(input, locale = "en-US") {
return new Date(input).toLocaleDateString(locale, {
year: "numeric",
month: "short",
day: "numeric",
});
}
輸出範例:Jan 29, 2026 — 乾淨、可讀、支援 locale。不用套件、不用 moment.js,用 Intl API 就夠了。
3. Class Joiner — 條件式 CSS 不亂成一團
不必用 clsx 或 classnames,五行程式就夠。
export function cx(...args) {
return args.filter(Boolean).join(" ");
}
範例:
<div className={cx("card", isSelected && "card--active", isDisabled && "card--muted")}>
falsy 會被濾掉,不會有 undefined 跑進 class 字串。
4. JSON Safety Net — 安全解析,不怕炸掉
JSON.parse 會 throw,這是它的個 性。包一層就不用再擔心。
export function safeParse(raw, fallback = null) {
try {
return JSON.parse(raw);
} catch {
return fallback;
}
}
情境:
const prefs = safeParse(localStorage.getItem("preferences"), { theme: "light" });
localStorage 壞掉、API 回傳怪東西?你會拿到合理預設值,而不是白畫面。
5. Emptiness Detector — 判斷物件是否真的空
{} 在 JavaScript 裡是 truthy,很多人會踩雷。
export function isEmptyObject(obj) {
return obj != null && typeof obj === "object" && Object.keys(obj).length === 0;
}
用途:
const errors = validate(formData);
if (isEmptyObject(errors)) {
submit(); // 可以放心送出
}
條件清楚,沒有模糊地帶。
6. Clipboard — 一鍵複製、零 drama
每個 SaaS 都有「複製到剪貼簿」按鈕,實作就這幾行。
export async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
return false;
}
}
用法:
const success = await copyText(shareableLink);
if (success) showToast("Copied!");
Clipboard API 是 async 且可能失敗(權限、非安全環境)。這裡把成功與失敗都處理好。
7. Capitalizer — 小函式,大 polish
把 "pending" 顯示成 "Pending"。就這樣。
export function capitalize(str) {
if (!str) return "";
return str[0].toUpperCase() + str.slice(1);
}
可以每次都 inline,但當你第三次寫 str.charAt(0).toUpperCase() + str.slice(1) 時就會想抽成函式。
8. Async 暫停 — JavaScript 的 sleep()
有時候就是要等:動畫、rate limit、或營造效果。
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
範例:指數退避重試
async function retryWithBackoff(fn, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (e) {
if (i === retries - 1) throw e;
await sleep(1000 * 2 ** i); // 1s, 2s, 4s
}
}
}
一行函式就能讓 async 流程好懂很多。
9. Deduplicator — 一行清掉陣列重複
合併使用者輸入、API 資料、query params 時,重複值常跑進來。用 Set 清掉。
export const dedupe = (arr) => [...new Set(arr)];
情境:
const allTags = dedupe([...userTags, ...suggestedTags, ...defaultTags]);
物件陣列需要更複雜的邏輯;對基本型別來說,Set 就夠了。
10. Silent Downloader — 程式觸發檔案下載
不必開 server endpoint、不用 window.open hack,用一個乾淨的下載觸發。
export function downloadFile(url, filename) {
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
}
用法:
downloadFile(blobUrl, "report-2026.csv");
適用 blob URL、data URL 和一般檔案路徑,由 <a> 負責實際下載。
目錄結構建議
在專案根目錄放一個 /lib 或 /utils:
/lib
├── debounce.ts
├── format.ts // formatDate, capitalize
├── guards.ts // isEmptyObject, safeParse
├── clipboard.ts
├── async.ts // sleep
├── array.ts // dedupe
└── dom.ts // downloadFile, cx
再用 barrel 匯出:
// lib/index.ts
export * from "./debounce";
export * from "./format";
export * from "./guards";
// ...
需要什麼就 import 什麼,tree-shaking 會處理其餘的。
小結
這些函式都不炫,重點就是:無聊、可靠、久經實戰。它們避免的是那種「大家都覺得很 trivial」所以 code review 沒人細看、卻會出事的 bug。
Trivial 的程式一樣要寫對。建立自己的工具帶、直接拿去用、改一改、再加自己的。最好寫的程式,就是以後不用再寫的那一段。