frontend-js-best-practices by sergiodxa/agent-skills
npx skills add https://github.com/sergiodxa/agent-skills --skill frontend-js-best-practices适用于 JavaScript 和 TypeScript 代码的性能优化和代码风格模式。包含 17 条规则,专注于减少不必要的计算、优化数据结构以及保持一致的约定。
在以下情况下参考这些指南:
在模块级别使用 const,在函数内部使用 let。
// 模块级别:基本类型使用 UPPER_SNAKE_CASE 的 const
const MAX_RETRIES = 3;
const userCache = new Map<string, User>();
// 函数内部:总是使用 let
function process(items: Item[]) {
let total = 0;
let result = [];
for (let item of items) {
total += item.price;
}
return { total, result };
}
对于命名函数,优先使用函数声明而非箭头函数。
// 良好:函数声明
function calculateTotal(items: Item[]): number {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
// 良好:内联回调使用箭头函数
let active = users.filter((u) => u.isActive);
// 良好:类型要求时使用箭头函数
const handler: ActionFunction = async ({ request }) => {
// ...
};
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
使用命名导出。避免默认导出(Remix 路由组件除外)。
// 不良:默认导出
export default function formatCurrency(amount: number) { ... }
// 良好:命名导出
export function formatCurrency(amount: number) { ... }
// 例外:Remix 路由使用名为 "Component" 的默认导出
export default function Component() { ... }
避免使用 as Type 进行类型转换。改用类型守卫或 Zod 验证。
// 不良:类型断言
let user = response.data as User;
// 良好:Zod 验证
let user = UserSchema.parse(response.data);
// 良好:类型守卫
if (isUser(response.data)) {
let user = response.data;
}
仅在代码无法表达信息时才添加注释。
// 不良:重复代码内容
// 设置用户姓名
let userName = user.name;
// 良好:解释业务规则
// 根据政策,金额低于 250 美元的交易不需要书面确认
if (transaction.amount < 250) {
return { requiresAcknowledgment: false };
}
使用 Set/Map 进行 O(1) 查找,而不是 Array 方法。
// 不良:每次检查 O(n)
const allowedIds = ["a", "b", "c"];
items.filter((item) => allowedIds.includes(item.id));
// 良好:每次检查 O(1)
const allowedIds = new Set(["a", "b", "c"]);
items.filter((item) => allowedIds.has(item.id));
为重复查找构建一次 Map。
// 不良:每次查找 O(n) = 总计 O(n*m)
orders.map((order) => ({
...order,
user: users.find((u) => u.id === order.userId),
}));
// 良好:每次查找 O(1) = 总计 O(n+m)
const userById = new Map(users.map((u) => [u.id, u]));
orders.map((order) => ({
...order,
user: userById.get(order.userId),
}));
使用 toSorted() 代替 sort() 以避免改变原数组。
// 不良:改变原数组
const sorted = users.sort((a, b) => a.name.localeCompare(b.name));
// 良好:创建新的排序数组
const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name));
将多个 filter/map 操作合并到一个循环中。
// 不良:3 次迭代
const admins = users.filter((u) => u.isAdmin);
const testers = users.filter((u) => u.isTester);
const inactive = users.filter((u) => !u.isActive);
// 良好:1 次迭代
const admins: User[] = [],
testers: User[] = [],
inactive: User[] = [];
for (const user of users) {
if (user.isAdmin) admins.push(user);
if (user.isTester) testers.push(user);
if (!user.isActive) inactive.push(user);
}
在循环中缓存对象属性。
// 不良:重复查找
for (let i = 0; i < arr.length; i++) {
process(obj.config.settings.value);
}
// 良好:缓存查找
const value = obj.config.settings.value;
const len = arr.length;
for (let i = 0; i < len; i++) {
process(value);
}
在模块级别的 Map 中缓存开销大的函数结果。
const slugifyCache = new Map<string, string>();
function cachedSlugify(text: string): string {
if (!slugifyCache.has(text)) {
slugifyCache.set(text, slugify(text));
}
return slugifyCache.get(text)!;
}
在内存中缓存 localStorage/sessionStorage 的读取。
const storageCache = new Map<string, string | null>();
function getLocalStorage(key: string) {
if (!storageCache.has(key)) {
storageCache.set(key, localStorage.getItem(key));
}
return storageCache.get(key);
}
当结果确定时提前返回。
// 不良:找到错误后继续执行
function validate(users: User[]) {
let error = "";
for (const user of users) {
if (!user.email) error = "Email required";
}
return error ? { error } : { valid: true };
}
// 良好:立即返回
function validate(users: User[]) {
for (const user of users) {
if (!user.email) return { error: "Email required" };
}
return { valid: true };
}
在进行开销大的比较之前先检查数组长度。
// 不良:即使长度不同也总是排序
function hasChanges(a: string[], b: string[]) {
return a.sort().join() !== b.sort().join();
}
// 良好:如果长度不同则提前返回
function hasChanges(a: string[], b: string[]) {
if (a.length !== b.length) return true;
let aSorted = a.toSorted();
let bSorted = b.toSorted();
return aSorted.some((v, i) => v !== bSorted[i]);
}
使用循环求最小/最大值,而不是排序。
// 不良:O(n log n)
const latest = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)[0];
// 良好:O(n)
let latest = projects[0];
for (const p of projects) {
if (p.updatedAt > latest.updatedAt) latest = p;
}
将 RegExp 创建提升到循环外部。
// 不良:每次迭代都创建正则表达式
items.forEach(item => {
if (/pattern/.test(item.text)) { ... }
})
// 良好:创建一次
const PATTERN = /pattern/
items.forEach(item => {
if (PATTERN.test(item.text)) { ... }
})
在写入之前批量读取 DOM 以避免布局抖动。
// 不良:读写交错导致强制重排
element.style.width = "100px";
const width = element.offsetWidth; // 强制重排
element.style.height = "200px";
// 良好:批量写入,然后读取
element.style.width = "100px";
element.style.height = "200px";
const { width, height } = element.getBoundingClientRect();
使用显式的 Result 类型表示成功/失败。
let result = success(data);
if (isFailure(result)) return handleError(result.error);
每周安装量
124
代码仓库
GitHub 星标数
79
首次出现
2026 年 1 月 28 日
安全审计
安装于
opencode104
codex101
github-copilot95
gemini-cli93
kimi-cli85
amp84
Performance optimization and code style patterns for JavaScript and TypeScript code. Contains 17 rules focused on reducing unnecessary computation, optimizing data structures, and maintaining consistent conventions.
Reference these guidelines when:
Use const at module level, let inside functions.
// Module level: const with UPPER_SNAKE_CASE for primitives
const MAX_RETRIES = 3;
const userCache = new Map<string, User>();
// Inside functions: always let
function process(items: Item[]) {
let total = 0;
let result = [];
for (let item of items) {
total += item.price;
}
return { total, result };
}
Prefer function declarations over arrow functions for named functions.
// Good: function declaration
function calculateTotal(items: Item[]): number {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
// Good: arrow for inline callbacks
let active = users.filter((u) => u.isActive);
// Good: arrow when type requires it
const handler: ActionFunction = async ({ request }) => {
// ...
};
Use named exports. Avoid default exports (except Remix route components).
// Bad: default export
export default function formatCurrency(amount: number) { ... }
// Good: named export
export function formatCurrency(amount: number) { ... }
// Exception: Remix routes use default export named "Component"
export default function Component() { ... }
Avoid as Type casts. Use type guards or Zod validation instead.
// Bad: type assertion
let user = response.data as User;
// Good: Zod validation
let user = UserSchema.parse(response.data);
// Good: type guard
if (isUser(response.data)) {
let user = response.data;
}
Only comment when adding info the code cannot express.
// Bad: restates the code
// Set the user's name
let userName = user.name;
// Good: explains business rule
// Transactions under $250 don't require written acknowledgment per policy
if (transaction.amount < 250) {
return { requiresAcknowledgment: false };
}
Use Set/Map for O(1) lookups instead of Array methods.
// Bad: O(n) per check
const allowedIds = ["a", "b", "c"];
items.filter((item) => allowedIds.includes(item.id));
// Good: O(1) per check
const allowedIds = new Set(["a", "b", "c"]);
items.filter((item) => allowedIds.has(item.id));
Build Map once for repeated lookups.
// Bad: O(n) per lookup = O(n*m) total
orders.map((order) => ({
...order,
user: users.find((u) => u.id === order.userId),
}));
// Good: O(1) per lookup = O(n+m) total
const userById = new Map(users.map((u) => [u.id, u]));
orders.map((order) => ({
...order,
user: userById.get(order.userId),
}));
Use toSorted() instead of sort() to avoid mutation.
// Bad: mutates original array
const sorted = users.sort((a, b) => a.name.localeCompare(b.name));
// Good: creates new sorted array
const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name));
Combine multiple filter/map into one loop.
// Bad: 3 iterations
const admins = users.filter((u) => u.isAdmin);
const testers = users.filter((u) => u.isTester);
const inactive = users.filter((u) => !u.isActive);
// Good: 1 iteration
const admins: User[] = [],
testers: User[] = [],
inactive: User[] = [];
for (const user of users) {
if (user.isAdmin) admins.push(user);
if (user.isTester) testers.push(user);
if (!user.isActive) inactive.push(user);
}
Cache object properties in loops.
// Bad: repeated lookups
for (let i = 0; i < arr.length; i++) {
process(obj.config.settings.value);
}
// Good: cached lookup
const value = obj.config.settings.value;
const len = arr.length;
for (let i = 0; i < len; i++) {
process(value);
}
Cache expensive function results in module-level Map.
const slugifyCache = new Map<string, string>();
function cachedSlugify(text: string): string {
if (!slugifyCache.has(text)) {
slugifyCache.set(text, slugify(text));
}
return slugifyCache.get(text)!;
}
Cache localStorage/sessionStorage reads in memory.
const storageCache = new Map<string, string | null>();
function getLocalStorage(key: string) {
if (!storageCache.has(key)) {
storageCache.set(key, localStorage.getItem(key));
}
return storageCache.get(key);
}
Return early when result is determined.
// Bad: continues after finding error
function validate(users: User[]) {
let error = "";
for (const user of users) {
if (!user.email) error = "Email required";
}
return error ? { error } : { valid: true };
}
// Good: returns immediately
function validate(users: User[]) {
for (const user of users) {
if (!user.email) return { error: "Email required" };
}
return { valid: true };
}
Check array length before expensive comparison.
// Bad: always sorts even when lengths differ
function hasChanges(a: string[], b: string[]) {
return a.sort().join() !== b.sort().join();
}
// Good: early return if lengths differ
function hasChanges(a: string[], b: string[]) {
if (a.length !== b.length) return true;
let aSorted = a.toSorted();
let bSorted = b.toSorted();
return aSorted.some((v, i) => v !== bSorted[i]);
}
Use loop for min/max instead of sort.
// Bad: O(n log n)
const latest = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)[0];
// Good: O(n)
let latest = projects[0];
for (const p of projects) {
if (p.updatedAt > latest.updatedAt) latest = p;
}
Hoist RegExp creation outside loops.
// Bad: creates regex every iteration
items.forEach(item => {
if (/pattern/.test(item.text)) { ... }
})
// Good: create once
const PATTERN = /pattern/
items.forEach(item => {
if (PATTERN.test(item.text)) { ... }
})
Batch DOM reads before writes to avoid layout thrashing.
// Bad: interleaved reads/writes force reflows
element.style.width = "100px";
const width = element.offsetWidth; // forces reflow
element.style.height = "200px";
// Good: batch writes, then read
element.style.width = "100px";
element.style.height = "200px";
const { width, height } = element.getBoundingClientRect();
Use an explicit Result type for success/failure.
let result = success(data);
if (isFailure(result)) return handleError(result.error);
Weekly Installs
124
Repository
GitHub Stars
79
First Seen
Jan 28, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode104
codex101
github-copilot95
gemini-cli93
kimi-cli85
amp84
Genkit JS 开发指南:AI 应用构建、错误排查与最佳实践
6,100 周安装
SEO专家技能包 | 技术性SEO、关键词研究、页面优化、外链建设与数据分析
146 周安装
高级前端开发工具集:React/Next.js项目脚手架、组件生成与性能优化
138 周安装
MLOps与机器学习工程专家 | 端到端ML流水线、模型部署与基础设施自动化指南
138 周安装
akshare财经数据技能:获取中国股票、指数、基金、期货实时历史数据
138 周安装
数据分析师技能:SQL查询、BI仪表板设计、KPI定义与商业智能分析
140 周安装
多平台付费广告审计与优化工具 - 覆盖Google、Meta、LinkedIn、TikTok、Microsoft广告
200 周安装