Appearance
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 = {
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'&': '&'
};
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最佳实践需要在实际项目中不断应用和改进。关键是要保持代码的一致性、可读性和可维护性,同时确保性能和安全性。