frontend-js-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

JavaScript Best Practices

JavaScript最佳实践

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.
JavaScript和TypeScript代码的性能优化与代码风格规范。包含17条规则,重点在于减少不必要的计算、优化数据结构以及保持一致的编码约定。

When to Apply

适用场景

Reference these guidelines when:
  • Writing loops or array operations
  • Working with data structures (Map, Set, arrays)
  • Manipulating the DOM directly
  • Caching values or function results
  • Optimizing hot code paths
  • Declaring variables or functions
在以下场景中参考这些指南:
  • 编写循环或数组操作代码时
  • 使用数据结构(Map、Set、数组)时
  • 直接操作DOM时
  • 缓存值或函数结果时
  • 优化热点代码路径时
  • 声明变量或函数时

Rules Summary

规则摘要

const-let-usage (MEDIUM) — @rules/const-let-usage.md

const-let-usage(中等)—— @rules/const-let-usage.md

Use
const
at module level,
let
inside functions.
typescript
// 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 };
}
模块级别使用
const
,函数内部使用
let
typescript
// 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 };
}

function-declarations (MEDIUM) — @rules/function-declarations.md

function-declarations(中等)—— @rules/function-declarations.md

Prefer function declarations over arrow functions for named functions.
typescript
// 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 }) => {
  // ...
};
对于具名函数,优先使用函数声明而非箭头函数。
typescript
// 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 }) => {
  // ...
};

no-default-exports (MEDIUM) — @rules/no-default-exports.md

no-default-exports(中等)—— @rules/no-default-exports.md

Use named exports. Avoid default exports (except Remix route components).
typescript
// 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() { ... }
使用具名导出。避免默认导出(Remix路由组件除外)。
typescript
// 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() { ... }

no-as-type-casts (HIGH) — @rules/no-as-type-casts.md

no-as-type-casts(高)—— @rules/no-as-type-casts.md

Avoid
as Type
casts. Use type guards or Zod validation instead.
typescript
// 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;
}
避免使用
as Type
类型断言。改用类型守卫或Zod验证。
typescript
// 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;
}

comments-meaningful-only (MEDIUM) — @rules/comments-meaningful-only.md

comments-meaningful-only(中等)—— @rules/comments-meaningful-only.md

Only comment when adding info the code cannot express.
typescript
// 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 };
}
仅在添加代码无法表达的信息时才写注释。
typescript
// 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 };
}

set-map-lookups (LOW-MEDIUM) — @rules/set-map-lookups.md

set-map-lookups(低-中等)—— @rules/set-map-lookups.md

Use Set/Map for O(1) lookups instead of Array methods.
typescript
// 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));
使用Set/Map实现O(1)时间复杂度的查找,而非数组方法。
typescript
// 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));

index-maps (LOW-MEDIUM) — @rules/index-maps.md

index-maps(低-中等)—— @rules/index-maps.md

Build Map once for repeated lookups.
typescript
// 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),
}));
一次性构建Map以支持重复查找。
typescript
// 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),
}));

tosorted-immutable (MEDIUM-HIGH) — @rules/tosorted-immutable.md

tosorted-immutable(中等-高)—— @rules/tosorted-immutable.md

Use
toSorted()
instead of
sort()
to avoid mutation.
typescript
// 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));
使用
toSorted()
而非
sort()
以避免数组突变。
typescript
// 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-iterations (LOW-MEDIUM) — @rules/combine-iterations.md

combine-iterations(低-中等)—— @rules/combine-iterations.md

Combine multiple filter/map into one loop.
typescript
// 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);
}
将多个filter/map操作合并为单次循环。
typescript
// 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-property-access (LOW-MEDIUM) — @rules/cache-property-access.md

cache-property-access(低-中等)—— @rules/cache-property-access.md

Cache object properties in loops.
typescript
// 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);
}
在循环中缓存对象属性。
typescript
// 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-function-results (MEDIUM) — @rules/cache-function-results.md

cache-function-results(中等)—— @rules/cache-function-results.md

Cache expensive function results in module-level Map.
typescript
const slugifyCache = new Map<string, string>();

function cachedSlugify(text: string): string {
  if (!slugifyCache.has(text)) {
    slugifyCache.set(text, slugify(text));
  }
  return slugifyCache.get(text)!;
}
在模块级Map中缓存开销较大的函数结果。
typescript
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-storage (LOW-MEDIUM) — @rules/cache-storage.md

cache-storage(低-中等)—— @rules/cache-storage.md

Cache localStorage/sessionStorage reads in memory.
typescript
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);
}
将localStorage/sessionStorage的读取结果缓存到内存中。
typescript
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);
}

early-exit (LOW-MEDIUM) — @rules/early-exit.md

early-exit(低-中等)—— @rules/early-exit.md

Return early when result is determined.
typescript
// 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 };
}
当结果确定时提前返回。
typescript
// 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 };
}

length-check-first (MEDIUM-HIGH) — @rules/length-check-first.md

length-check-first(中等-高)—— @rules/length-check-first.md

Check array length before expensive comparison.
typescript
// 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]);
}
在执行开销较大的比较前先检查数组长度。
typescript
// 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]);
}

min-max-loop (LOW) — @rules/min-max-loop.md

min-max-loop(低)—— @rules/min-max-loop.md

Use loop for min/max instead of sort.
typescript
// 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;
}
使用循环获取最小值/最大值,而非排序。
typescript
// 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 (LOW-MEDIUM) — @rules/hoist-regexp.md

hoist-regexp(低-中等)—— @rules/hoist-regexp.md

Hoist RegExp creation outside loops.
typescript
// 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)) { ... }
})
将RegExp的创建提升到循环外部。
typescript
// 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-css (MEDIUM) — @rules/batch-dom-css.md

batch-dom-css(中等)—— @rules/batch-dom-css.md

Batch DOM reads before writes to avoid layout thrashing.
typescript
// 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();
批量执行DOM读取后再执行写入操作,避免布局抖动。
typescript
// 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();

result-type (MEDIUM) — @rules/result-type.md

result-type(中等)—— @rules/result-type.md

Use an explicit
Result
type for success/failure.
typescript
let result = success(data);
if (isFailure(result)) return handleError(result.error);
使用显式的
Result
类型表示成功/失败结果。
typescript
let result = success(data);
if (isFailure(result)) return handleError(result.error);