好的编码规范不是为了限制创造力,而是让六个月后的自己和同事都能快速读懂代码。本文整理前端日常开发中最容易踩坑的规范,配合 NO / YES 对比示例。

变量

不要定义用不到的变量

// ❌ 定义了却从不使用
let kpi = 4;
function calc(a, b) {
  const c = a + b;
  const d = c + 1;
  const e = d + a;
  return e;
}

// ✅ 只保留必要变量
function calc(a, b) {
  return 2 * a + b + 1;
}

命名要有语义,避免神秘缩写

// ❌ 缩写让人猜含义
let fName = 'jackie';
let lName = 'willen';

// ✅ 见名知意
let firstName = 'jackie';
let lastName = 'willen';

魔法数字提取为常量

// ❌ 8 是什么意思?
if (value.length < 8) { /* ... */ }

// ✅ 用常量表达业务含义
const MAX_INPUT_LENGTH = 8;
if (value.length < MAX_INPUT_LENGTH) { /* ... */ }

避免啰嗦命名

// ❌
let nameString;
let theUsers;

// ✅
let name;
let users;

复杂表达式拆成有意义的变量

// ❌ 正则结果含义不明
const address = 'One Infinite Loop, Cupertino 95014';
const regex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
  address.match(regex)[1],
  address.match(regex)[2]
);

// ✅ 解构 + 兜底
const [, city, zipCode] = address.match(regex) || [];
saveCityZipCode(city, zipCode);

求值变量做好兜底

// ❌ fullName 只有一项时会得到 undefined
const lastName = fullName[1];
if (lastName.length > MIN_NAME_LENGTH) { /* ... */ }

// ✅ 兜底为空字符串
const lastName = fullName[1] || '';
if (lastName.length > MIN_NAME_LENGTH) { /* ... */ }

减少全局变量污染

// ❌ 多个文件修改 window.name
// name.js
window.name = 'a';
// hello.js
window.name = 'b';

// ✅ 使用模块作用域或状态管理
// store/user.js
export const userStore = { name: 'a' };

函数

布尔返回值以 is / has / can / should 开头

// ❌ 看不出返回类型
function showFriendsList() { /* ... */ }

// ✅
function shouldShowFriendsList() { return true; }
function isEmpty(list) { return list.length === 0; }
function hasPermission(user) { return user.role === 'admin'; }

优先写纯函数

// ❌ 依赖外部 API,同样入参结果不稳定
function plusAbc(a, b) {
  const c = fetch('/api').then(r => r.json());
  return a + b + c;
}

// ✅ 输入确定,输出确定
function sum(a, b, c) {
  return a + b + c;
}

布尔参数改为对象参数

// ❌ true/false 含义不清
page.getSVG(api, true, false);

// ✅
page.getSVG({ api, animated: true, cache: false });

函数名以动词开头

// ❌
function emlU(user) { /* ... */ }

// ✅
function sendEmailToUser(user) { /* ... */ }

一个函数只做一件事

// ❌ 查询、判断、发送混在一起
function sendEmailToClients(clients) {
  clients.forEach(client => {
    const record = database.lookup(client);
    if (record.isActive()) {
      email(client);
    }
  });
}

// ✅ 拆分职责
function isActiveClient(client) {
  return database.lookup(client).isActive();
}

function sendEmailToActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

用 map / filter 替代无意义的 for 循环

// ❌
for (let i = 0; i < a.length; i++) {
  a[i] = a[i] + 1;
}

// ✅
const b = a.map(item => item + 1);

减少深层 if-else

// ❌
if (status === 1) { /* ... */ }
else if (status === 2) { /* ... */ }
else if (status === 3) { /* ... */ }
else { /* ... */ }

// ✅ 策略表
const handlers = {
  1: handlePending,
  2: handleActive,
  3: handleClosed,
};
(handlers[status] || handleDefault)();

异步与错误处理

// ❌ 不处理异常
async function loadData() {
  const res = await fetch('/api/data');
  return res.json();
}

// ✅ 明确错误边界
async function loadData() {
  try {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error('loadData failed:', err);
    return null;
  }
}

组件与文件(Vue / React 通用)

规范说明
单文件单组件一个 .vue / .tsx 对应一个主组件
文件名 PascalCaseUserProfile.vueOrderList.tsx
Props 声明类型Vue 用 defineProps,React 用 TypeScript interface
事件名 kebab-caseVue:@update-count;React:onUpdateCount

ESLint 推荐配置

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:vue/vue3-recommended"
  ],
  "rules": {
    "no-unused-vars": "error",
    "prefer-const": "error",
    "no-var": "error",
    "eqeqeq": ["error", "always"]
  }
}

配合 Prettier 统一格式,提交前通过 lint-staged 自动检查:

{
  "lint-staged": {
    "*.{js,ts,vue}": ["eslint --fix", "prettier --write"]
  }
}

代码审查清单

提交 PR 前自检:

  • [ ] 无未使用变量与 import
  • [ ] 无魔法数字,业务常量已提取
  • [ ] 函数职责单一,命名可读
  • [ ] 异步调用有错误处理
  • [ ] 无 console.log 调试残留
  • [ ] ESLint / TypeScript 检查通过

小结

编码规范的核心就三条:命名说清楚、函数做一件事、边界要兜底。团队可以先从 ESLint 规则落地,再逐步补充本文中的约定,比一次性写几百页文档更有效。