Skip to content
On this page

JavaScript 最佳实践

JavaScript最佳实践是开发高质量、可维护、可扩展代码的指导原则。遵循这些实践可以帮助开发者编写更健壮、性能更好、更易维护的代码。

代码风格和格式化

一致的命名约定

javascript
// 1. 变量和函数使用camelCase
const userName = 'John';
const userAge = 30;

function calculateTotalPrice(items) {
  return items.reduce((total, item) => total + item.price, 0);
}

// 2. 常量使用UPPER_SNAKE_CASE
const API_BASE_URL = 'https://api.example.com';
const MAX_RETRY_ATTEMPTS = 3;

// 3. 构造函数和类使用PascalCase
class UserManager {
  constructor() {
    this.users = [];
  }
}

function UserProfile(name, email) {
  this.name = name;
  this.email = email;
}

// 4. 布尔值使用is, has, can等前缀
const isLoggedIn = true;
const hasPermission = false;
const canEdit = true;

// 5. 私有成员使用下划线前缀(约定)
class DataStore {
  constructor() {
    this._data = [];
    this._isInitialized = false;
  }
  
  get data() {
    return this._data;
  }
}

适当的注释

javascript
// 1. 解释复杂逻辑
function calculateTax(amount, taxRate, isTaxExempt) {
  // 对于免税商品,税率为0
  if (isTaxExempt) {
    return 0;
  }
  
  // 计算税额,保留两位小数
  return Math.round(amount * taxRate * 100) / 100;
}

// 2. 使用JSDoc记录函数
/**
 * 计算购物车总价
 * @param {Array<Object>} items - 商品数组,每个商品包含price和quantity属性
 * @param {number} taxRate - 税率(小数形式,如0.08表示8%)
 * @param {boolean} includeTax - 是否包含税
 * @returns {number} 计算后的总价
 */
function calculateCartTotal(items, taxRate = 0, includeTax = false) {
  const subtotal = items.reduce((total, item) => {
    return total + (item.price * item.quantity);
  }, 0);
  
  if (includeTax) {
    return subtotal * (1 + taxRate);
  }
  
  return subtotal;
}

// 3. 标记待办事项
// TODO: 实现用户认证功能
// FIXME: 修复在IE浏览器中的兼容性问题
// HACK: 临时解决方案,需要重构
// NOTE: 这个函数在高并发情况下可能有性能问题

代码格式化

javascript
// 1. 一致的缩进(推荐2或4个空格)
function processData(data) {
  if (data) {
    return data.map(item => ({
      id: item.id,
      name: item.name,
      active: item.status === 'active'
    }));
  }
  return [];
}

// 2. 适当的空行分隔逻辑块
function userAuthentication(username, password) {
  // 验证输入
  if (!username || !password) {
    throw new Error('用户名和密码不能为空');
  }

  // 检查用户是否存在
  const user = findUser(username);
  if (!user) {
    throw new Error('用户不存在');
  }

  // 验证密码
  if (!verifyPassword(password, user.passwordHash)) {
    throw new Error('密码错误');
  }

  return generateAuthToken(user);
}

// 3. 对象和数组的格式化
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + token
  }
};

const items = [
  { id: 1, name: 'Item 1', price: 10.99 },
  { id: 2, name: 'Item 2', price: 15.99 },
  { id: 3, name: 'Item 3', price: 8.99 }
];

函数设计最佳实践

单一职责原则

javascript
// 不好的做法 - 一个函数做太多事情
function processUserData(users) {
  // 数据验证
  const validUsers = users.filter(user => user.email && user.name);
  
  // 数据转换
  const processedUsers = validUsers.map(user => ({
    ...user,
    email: user.email.toLowerCase(),
    name: user.name.toUpperCase()
  }));
  
  // 数据存储
  saveToDatabase(processedUsers);
  
  return processedUsers;
}

// 好的做法 - 每个函数只负责一个职责
function validateUsers(users) {
  return users.filter(user => user.email && user.name);
}

function transformUsers(users) {
  return users.map(user => ({
    ...user,
    email: user.email.toLowerCase(),
    name: user.name.toUpperCase()
  }));
}

function saveUsers(users) {
  return saveToDatabase(users);
}

function processUserData(users) {
  const validUsers = validateUsers(users);
  const transformedUsers = transformUsers(validUsers);
  saveUsers(transformedUsers);
  return transformedUsers;
}

函数参数最佳实践

javascript
// 1. 限制参数数量(推荐不超过3个)
// 不好的做法
function createUser(name, email, age, address, phone, isActive, role, permissions) {
  // 函数参数过多,难以维护
}

// 好的做法 - 使用对象参数
function createUser(userData) {
  const {
    name,
    email,
    age,
    address = '',
    phone = '',
    isActive = true,
    role = 'user',
    permissions = []
  } = userData;
  
  return {
    name,
    email,
    age,
    address,
    phone,
    isActive,
    role,
    permissions
  };
}

// 2. 使用默认参数而不是条件检查
// 不好的做法
function greetUser(name) {
  name = name || 'Guest';
  return `Hello, ${name}!`;
}

// 好的做法
function greetUser(name = 'Guest') {
  return `Hello, ${name}!`;
}

// 3. 使用解构参数
function configureServer({ port = 3000, host = 'localhost', ssl = false } = {}) {
  return {
    port,
    host,
    ssl
  };
}

// 使用时可以只传需要的参数
const config = configureServer({ port: 8080 });

避免副作用

javascript
// 有副作用的函数(不好的做法)
let globalCounter = 0;

function addItemToList(list, item) {
  list.push(item); // 修改了传入的数组
  globalCounter++; // 修改了全局变量
  return list.length;
}

// 纯函数(好的做法)
function addItemToList(list, item) {
  return [...list, item]; // 返回新数组,不修改原数组
}

// 使用不可变数据结构
function updateUser(users, userId, updates) {
  return users.map(user => 
    user.id === userId ? { ...user, ...updates } : user
  );
}

对象和数组最佳实践

使用现代ES6+特性

javascript
// 1. 使用解构赋值
const user = { name: 'John', age: 30, email: 'john@example.com' };

// 不好的做法
const userName = user.name;
const userAge = user.age;

// 好的做法
const { name: userName, age: userAge, email } = user;

// 2. 使用扩展运算符
const originalArray = [1, 2, 3];
const newArray = [...originalArray, 4, 5]; // 而不是push

const originalObj = { name: 'John', age: 30 };
const newObj = { ...originalObj, email: 'john@example.com' }; // 而不是Object.assign

// 3. 使用模板字符串
const name = 'John';
const age = 30;

// 不好的做法
const message = 'Hello, my name is ' + name + ' and I am ' + age + ' years old.';
const message2 = 'Hello, my name is '.concat(name, ' and I am ', age, ' years old.');

// 好的做法
const message = `Hello, my name is ${name} and I am ${age} years old.`;

数组操作最佳实践

javascript
// 1. 使用合适的数组方法
const users = [
  { id: 1, name: 'John', active: true },
  { id: 2, name: 'Jane', active: false },
  { id: 3, name: 'Bob', active: true }
];

// 查找单个元素
const user = users.find(u => u.id === 2);

// 过滤元素
const activeUsers = users.filter(u => u.active);

// 转换元素
const usernames = users.map(u => u.name);

// 检查条件
const hasActiveUsers = users.some(u => u.active);

// 检查所有元素
const allActive = users.every(u => u.active);

// 聚合数据
const totalUsers = users.reduce((count, user) => count + 1, 0);

// 2. 避免在循环中进行复杂操作
// 不好的做法
const processed = [];
for (let i = 0; i < users.length; i++) {
  if (users[i].active) {
    const processedUser = {
      ...users[i],
      displayName: users[i].name.toUpperCase(),
      ageGroup: users[i].age >= 18 ? 'adult' : 'minor'
    };
    processed.push(processedUser);
  }
}

// 好的做法
const processed = users
  .filter(user => user.active)
  .map(user => ({
    ...user,
    displayName: user.name.toUpperCase(),
    ageGroup: user.age >= 18 ? 'adult' : 'minor'
  }));

错误处理最佳实践

适当的错误处理

javascript
// 1. 具体的错误类型
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

class NetworkError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'NetworkError';
    this.status = status;
  }
}

// 2. 验证输入
function processUser(userData) {
  if (!userData || typeof userData !== 'object') {
    throw new ValidationError('用户数据必须是对象', 'userData');
  }
  
  if (!userData.name) {
    throw new ValidationError('姓名是必需的', 'name');
  }
  
  if (!userData.email || !userData.email.includes('@')) {
    throw new ValidationError('邮箱格式不正确', 'email');
  }
  
  return {
    name: userData.name,
    email: userData.email.toLowerCase().trim()
  };
}

// 3. 优雅的错误处理
async function safeApiCall(url, options = {}) {
  try {
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    });
    
    if (!response.ok) {
      throw new NetworkError(
        `HTTP ${response.status}: ${response.statusText}`, 
        response.status
      );
    }
    
    return await response.json();
  } catch (error) {
    if (error instanceof NetworkError) {
      console.error('网络错误:', error.message);
    } else if (error instanceof ValidationError) {
      console.error('验证错误:', error.message);
    } else {
      console.error('未知错误:', error);
    }
    
    // 重新抛出或返回默认值
    throw error;
  }
}

Promise和async/await错误处理

javascript
// 1. 使用try-catch处理async函数
async function fetchUserData(userId) {
  try {
    const userResponse = await fetch(`/api/users/${userId}`);
    if (!userResponse.ok) {
      throw new Error(`获取用户失败: ${userResponse.status}`);
    }
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/users/${userId}/posts`);
    if (!postsResponse.ok) {
      throw new Error(`获取帖子失败: ${postsResponse.status}`);
    }
    const posts = await postsResponse.json();
    
    return { user, posts };
  } catch (error) {
    console.error('获取用户数据失败:', error);
    throw error; // 让调用者处理
  }
}

// 2. 使用Promise.allSettled处理并行请求
async function fetchMultipleResources() {
  const promises = [
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/comments').then(r => r.json())
  ];
  
  const results = await Promise.allSettled(promises);
  
  const data = {};
  const errors = [];
  
  results.forEach((result, index) => {
    const keys = ['users', 'posts', 'comments'];
    if (result.status === 'fulfilled') {
      data[keys[index]] = result.value;
    } else {
      errors.push({ resource: keys[index], error: result.reason });
      data[keys[index]] = null;
    }
  });
  
  if (errors.length > 0) {
    console.warn('部分资源获取失败:', errors);
  }
  
  return data;
}

异步编程最佳实践

避免回调地狱

javascript
// 回调地狱(不好的做法)
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getEvenEvenMoreData(c, function(d) {
        console.log('最终结果:', d);
      });
    });
  });
});

// 使用Promise(好的做法)
getData()
  .then(a => getMoreData(a))
  .then(b => getEvenMoreData(b))
  .then(c => getEvenEvenMoreData(c))
  .then(d => console.log('最终结果:', d))
  .catch(error => console.error('错误:', error));

// 使用async/await(更好的做法)
async function processData() {
  try {
    const a = await getData();
    const b = await getMoreData(a);
    const c = await getEvenMoreData(b);
    const d = await getEvenEvenMoreData(c);
    console.log('最终结果:', d);
  } catch (error) {
    console.error('错误:', error);
  }
}

合理使用并发

javascript
// 1. 区分串行和并行
async function sequentialProcessing() {
  // 串行处理 - 每个请求等待前一个完成
  const result1 = await fetch('/api/data1');
  const result2 = await fetch('/api/data2'); // 等待result1完成后才开始
  const result3 = await fetch('/api/data3'); // 等待result2完成后才开始
  return [result1, result2, result3];
}

async function parallelProcessing() {
  // 并行处理 - 所有请求同时开始
  const [result1, result2, result3] = await Promise.all([
    fetch('/api/data1'),
    fetch('/api/data2'),
    fetch('/api/data3')
  ]);
  return [result1, result2, result3];
}

// 2. 控制并发数量
class ConcurrencyController {
  constructor(limit = 3) {
    this.limit = limit;
    this.running = 0;
    this.queue = [];
  }
  
  async add(asyncFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        asyncFn,
        resolve,
        reject
      });
      this.process();
    });
  }
  
  async process() {
    if (this.running >= this.limit || this.queue.length === 0) {
      return;
    }
    
    this.running++;
    const { asyncFn, resolve, reject } = this.queue.shift();
    
    try {
      const result = await asyncFn();
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      this.running--;
      this.process();
    }
  }
}

DOM操作最佳实践

高效的DOM操作

javascript
// 1. 减少DOM访问
// 不好的做法
const list = document.getElementById('myList');
for (let i = 0; i < 1000; i++) {
  list.innerHTML += `<li>Item ${i}</li>`; // 每次都重新解析和构建DOM
}

// 好的做法 - 使用文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
document.getElementById('myList').appendChild(fragment);

// 2. 使用事件委托
// 不好的做法 - 为每个元素添加监听器
document.querySelectorAll('.button').forEach(button => {
  button.addEventListener('click', handleClick);
});

// 好的做法 - 事件委托
document.addEventListener('click', function(event) {
  if (event.target.matches('.button')) {
    handleClick(event);
  }
});

// 3. 避免强制同步布局
const element = document.getElementById('myElement');

// 不好的做法
element.style.left = '10px';
console.log(element.offsetLeft); // 强制布局
element.style.top = '10px';
console.log(element.offsetTop);  // 再次强制布局

// 好的做法
element.style.left = '10px';
element.style.top = '10px';
const position = element.getBoundingClientRect(); // 一次性获取多个值

性能优化最佳实践

优化循环和算法

javascript
// 1. 优化循环
const items = new Array(10000).fill(1);

// 不好的做法
for (let i = 0; i < items.length; i++) {
  // 每次都访问items.length
}

// 好的做法 - 缓存长度
for (let i = 0, len = items.length; i < len; i++) {
  // 长度已缓存
}

// 2. 选择合适的数据结构
const validIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 不好的做法 - O(n)查找
if (validIds.includes(5)) {
  // 处理
}

// 好的做法 - O(1)查找
const validIdsSet = new Set(validIds);
if (validIdsSet.has(5)) {
  // 处理
}

// 3. 避免不必要的计算
// 不好的做法
function processArray(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    const expensiveResult = performExpensiveOperation(arr[i]);
    if (expensiveResult > threshold) {
      result.push(expensiveResult);
    }
  }
  return result;
}

// 好的做法 - 提前检查
function processArray(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    // 先进行快速检查
    if (quickCheck(arr[i])) {
      const expensiveResult = performExpensiveOperation(arr[i]);
      if (expensiveResult > threshold) {
        result.push(expensiveResult);
      }
    }
  }
  return result;
}

函数优化

javascript
// 1. 使用记忆化避免重复计算
function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const fibonacci = memoize(function(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

// 2. 避免在循环中创建函数
// 不好的做法
function createHandlers(items) {
  return items.map(item => {
    return function() {
      // 每次都创建新函数
      console.log(item.value);
    };
  });
}

// 好的做法
function createHandler(item) {
  return function() {
    console.log(item.value);
  };
}

function createHandlers(items) {
  return items.map(item => createHandler(item));
}

模块化和架构最佳实践

模块设计

javascript
// 1. 单一职责的模块
// user-validation.js - 只负责用户验证
export function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

export function validatePassword(password) {
  return password.length >= 8;
}

// user-service.js - 只负责用户服务
import { validateEmail, validatePassword } from './user-validation.js';

export class UserService {
  async createUser(userData) {
    if (!validateEmail(userData.email)) {
      throw new Error('Invalid email');
    }
    if (!validatePassword(userData.password)) {
      throw new Error('Invalid password');
    }
    
    // 创建用户的逻辑
    return await this.saveUser(userData);
  }
  
  async saveUser(userData) {
    // 保存用户的逻辑
  }
}

// 2. 清晰的模块接口
// api-client.js
export class ApiClient {
  constructor(baseURL, defaultHeaders = {}) {
    this.baseURL = baseURL;
    this.defaultHeaders = defaultHeaders;
  }
  
  async get(endpoint, params = {}) {
    const queryString = new URLSearchParams(params).toString();
    const url = queryString ? `${endpoint}?${queryString}` : endpoint;
    return this.request(url, { method: 'GET' });
  }
  
  async post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
  
  async request(endpoint, options = {}) {
    const config = {
      headers: { ...this.defaultHeaders, ...options.headers },
      ...options
    };
    
    const response = await fetch(`${this.baseURL}${endpoint}`, config);
    
    if (!response.ok) {
      throw new Error(`API请求失败: ${response.status}`);
    }
    
    return response.json();
  }
}

状态管理

javascript
// 使用观察者模式管理状态
class Store {
  constructor(initialState = {}) {
    this.state = initialState;
    this.listeners = [];
  }
  
  getState() {
    return { ...this.state };
  }
  
  setState(newState) {
    this.state = { ...this.state, ...newState };
    this.notifyListeners();
  }
  
  subscribe(listener) {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }
  
  notifyListeners() {
    this.listeners.forEach(listener => listener(this.getState()));
  }
}

// 使用示例
const store = new Store({ count: 0, user: null });

const unsubscribe = store.subscribe((state) => {
  console.log('状态更新:', state);
});

store.setState({ count: 1 });

测试最佳实践

编写可测试的代码

javascript
// 1. 分离副作用
// 不好的做法 - 难以测试
function processOrder(order) {
  const tax = calculateTax(order.amount);
  const total = order.amount + tax;
  
  // 副作用 - 直接发送邮件
  sendConfirmationEmail(order.customer.email, total);
  
  // 副作用 - 直接保存到数据库
  saveOrderToDatabase({ ...order, total });
  
  return total;
}

// 好的做法 - 分离逻辑和副作用
function calculateOrderTotal(order) {
  const tax = calculateTax(order.amount);
  return order.amount + tax;
}

function processOrder(order, services) {
  const total = calculateOrderTotal(order);
  
  // 使用依赖注入
  services.emailService.sendConfirmation(order.customer.email, total);
  services.databaseService.saveOrder({ ...order, total });
  
  return total;
}

// 2. 易于测试的函数
export function formatCurrency(amount, currency = 'USD') {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: currency
  }).format(amount);
}

// 可以轻松测试
// test('formatCurrency formats correctly', () => {
//   expect(formatCurrency(1000)).toBe('$1,000.00');
//   expect(formatCurrency(1000, 'EUR')).toBe('€1,000.00');
// });

安全最佳实践

防止常见安全漏洞

javascript
// 1. 防止XSS攻击
function safeInsertHTML(element, content) {
  // 不直接插入HTML
  // element.innerHTML = content; // 危险!
  
  // 安全的方式
  element.textContent = content; // 会转义HTML标签
  
  // 或使用DOM方法
  const textNode = document.createTextNode(content);
  element.appendChild(textNode);
}

// 2. 输入验证和清理
function validateAndSanitizeInput(input) {
  // 长度限制
  if (input.length > 1000) {
    throw new Error('输入过长');
  }
  
  // 特殊字符检查
  if (/[<>'"&]/.test(input)) {
    // 移除或转义危险字符
    return input.replace(/[<>'"&]/g, char => {
      const escapeMap = {
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#x27;',
        '&': '&amp;'
      };
      return escapeMap[char];
    });
  }
  
  return input;
}

// 3. 安全的API调用
class SecureApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }
  
  async makeRequest(endpoint, options = {}) {
    // 验证端点
    if (!this.isValidEndpoint(endpoint)) {
      throw new Error('Invalid endpoint');
    }
    
    // 添加安全头
    const secureOptions = {
      ...options,
      headers: {
        ...options.headers,
        'X-Requested-With': 'XMLHttpRequest',
        'Content-Type': 'application/json'
      }
    };
    
    const response = await fetch(`${this.baseURL}${endpoint}`, secureOptions);
    
    // 验证响应
    if (response.headers.get('Content-Type')?.includes('application/json')) {
      return response.json();
    }
    
    throw new Error('Unexpected response type');
  }
  
  isValidEndpoint(endpoint) {
    // 只允许预定义的端点
    const allowedEndpoints = ['/api/users', '/api/posts', '/api/comments'];
    return allowedEndpoints.includes(endpoint);
  }
}

代码质量工具

使用ESLint和Prettier

javascript
// .eslintrc.js 示例配置
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended' // 如果使用TypeScript
  ],
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module'
  },
  rules: {
    'no-console': 'warn',
    'no-unused-vars': 'error',
    'prefer-const': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'prefer-template': 'error'
  }
};

// prettier.config.js 示例配置
module.exports = {
  semi: true,
  trailingComma: 'es5',
  singleQuote: true,
  printWidth: 80,
  tabWidth: 2,
  useTabs: false
};

JavaScript最佳实践需要在实际项目中不断应用和改进。关键是要保持代码的一致性、可读性和可维护性,同时确保性能和安全性。