JavaScript 最佳实践

代码质量原则

1. 保持函数简单

// ❌ 不好的做法 - 函数太长,做了太多事情
function processUserData(data) {
    let cleanedData = [];
    for (let i = 0; i < data.length; i++) {
        let item = data[i];
        if (item.name) {
            item.name = item.name.trim();
        }
        if (item.age && item.age < 0) {
            item.age = 0;
        }
        if (item.email && !item.email.includes('@')) {
            delete item.email;
        }
        cleanedData.push(item);
    }
    return cleanedData;
}

// ✅ 好的做法 - 拆分成小函数
const cleanUserName = (name) => name?.trim();
const normalizeAge = (age) => Math.max(0, age || 0);
const validateEmail = (email) => email?.includes('@') ? email : null;

const cleanUserData = (data) => data.map(user => ({
    ...user,
    name: cleanUserName(user.name),
    age: normalizeAge(user.age),
    email: validateEmail(user.email)
}));

const filterValidUsers = (users) =>
    users.filter(user => user.email && user.name);

2. 使用有意义的变量名

// ❌ 不好的做法
const d = new Date();
const a = users.filter(u => u.active);
const r = res.json();

// ✅ 好的做法
const currentDate = new Date();
const activeUsers = users.filter(user => user.isActive);
const response = await fetch(url);
const data = await response.json();

3. 避免魔法数字

// ❌ 不好的做法
if (status === 1) {
    // 处理成功状态
} else if (status === 2) {
    // 处理失败状态
}

// ✅ 好的做法
const STATUS = {
    SUCCESS: 1,
    FAILURE: 2,
    PENDING: 3
};

if (status === STATUS.SUCCESS) {
    // 处理成功状态
} else if (status === STATUS.FAILURE) {
    // 处理失败状态
}

// 或使用字符串常量
const API_STATUS = {
    SUCCESS: 'success',
    FAILURE: 'failure',
    PENDING: 'pending'
};

性能优化

1. 避免不必要的重新渲染

// ❌ 不好的做法 - 每次渲染都创建新数组
function UserList({ users }) {
    return (
        
    {users.map(user => (
  • {user.name}
  • ))}
); } // ✅ 好的做法 - 使用 useMemo 缓存 function UserList({ users }) { const userItems = useMemo(() => users.map(user => (
  • {user.name}
  • )), [users] ); return
      {userItems}
    ; } // ✅ 或使用 React.memo const UserItem = React.memo(function UserItem({ user }) { return
  • {user.name}
  • ; });

    2. 防抖和节流

    // 防抖 - 延迟执行,只在停止触发后执行
    const debouncedSearch = debounce((query) => {
        performSearch(query);
    }, 300);
    
    // 节流 - 固定时间间隔执行
    const throttledScroll = throttle(() => {
        handleScroll();
    }, 100);
    
    // 使用示例
    searchInput.addEventListener('input', (e) => {
        debouncedSearch(e.target.value);
    });
    
    window.addEventListener('scroll', throttledScroll);

    3. 事件委托

    // ❌ 不好的做法 - 为每个元素添加事件监听器
    const buttons = document.querySelectorAll('.button');
    buttons.forEach(btn => {
        btn.addEventListener('click', handleClick);
    });
    
    // ✅ 好的做法 - 使用事件委托
    const container = document.querySelector('.container');
    container.addEventListener('click', (e) => {
        if (e.target.matches('.button')) {
            handleClick(e.target);
        }
    });
    
    // React 中的事件委托
    function ButtonList({ items, onClick }) {
        const handleClick = (e) => {
            const index = e.target.dataset.index;
            onClick(parseInt(index));
        };
    
        return (
            
      {items.map((item, index) => (
    • {item}
    • ))}
    ); }

    错误处理

    // ✅ 好的做法 - 自定义错误类
    class APIError extends Error {
        constructor(message, statusCode, code) {
            super(message);
            this.statusCode = statusCode;
            this.code = code;
            this.name = 'APIError';
        }
    }
    
    // ✅ 好的做法 - 优雅的错误处理
    async function fetchData(url) {
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new APIError(
                    `HTTP Error: ${response.status}`,
                    response.status,
                    'HTTP_ERROR'
                );
            }
            return await response.json();
        } catch (error) {
            if (error instanceof APIError) {
                // API 错误处理
                console.error('API Error:', error.message);
                throw error;
            } else if (error instanceof TypeError) {
                // 网络错误处理
                console.error('Network Error:', error.message);
                throw new APIError(
                    '网络连接失败',
                    0,
                    'NETWORK_ERROR'
                );
            }
            throw error;
        }
    }
    
    // ✅ 好的做法 - 错误边界 (React)
    class ErrorBoundary extends React.Component {
        constructor(props) {
            super(props);
            this.state = { hasError: false, error: null };
        }
    
        static getDerivedStateFromError(error) {
            return { hasError: true, error };
        }
    
        componentDidCatch(error, errorInfo) {
            console.error('Error caught by boundary:', error, errorInfo);
            // 可以在这里上报错误
        }
    
        render() {
            if (this.state.hasError) {
                return (
                    

    出错了

    {this.state.error?.message}

    ); } return this.props.children; } }

    安全性

    1. XSS 防护

    // ❌ 不好的做法 - 直接使用 dangerouslySetInnerHTML
    
    // ✅ 好的做法 - 清理 HTML import DOMPurify from 'dompurify'; const cleanContent = DOMPurify.sanitize(userContent, { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'], ALLOWED_ATTR: ['href'] }); // ✅ 好的做法 - 使用文本内容
    {userContent}
    // ✅ 好的做法 - 使用模板字符串
    {userInput}

    2. 敏感信息保护

    // ❌ 不好的做法 - 在 URL 中传递敏感信息
    fetch('/api/users?password=secret123');
    
    // ❌ 不好的做法 - 在 localStorage 存储敏感信息
    localStorage.setItem('password', 'secret123');
    
    // ✅ 好的做法 - 使用 POST 请求
    fetch('/api/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ username, password })
    });
    
    // ✅ 好的做法 - 使用 sessionStorage 或 sessionStorage
    sessionStorage.setItem('authToken', token);
    sessionStorage.removeItem('authToken'); // 用完即删
    
    // ✅ 好的做法 - 使用 HTTP-only Cookie
    // 由服务器设置,JavaScript 无法访问

    3. 验证和清理

    // ✅ 好的做法 - 服务端验证 + 客户端验证
    const validateEmail = (email) => {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    };
    
    const sanitizeInput = (input) => {
        if (typeof input !== 'string') return '';
        return input.trim().slice(0, 1000); // 限制长度
    };
    
    const processUserInput = (input) => {
        const sanitized = sanitizeInput(input);
        if (!sanitized) {
            throw new Error('输入不能为空');
        }
        return sanitized;
    };

    代码组织

    1. 模块化

    // ✅ 好的做法 - 按功能组织代码
    // api/user.js
    export const fetchUsers = () => fetch('/api/users').then(r => r.json());
    export const createUser = (data) => fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(data)
    });
    
    // utils/validation.js
    export const validateEmail = (email) => { /* ... */ };
    export const validatePhone = (phone) => { /* ... */ };
    
    // components/UserList.js
    import { fetchUsers } from '../api/user';
    import { validateEmail } from '../utils/validation';
    
    export function UserList() {
        // ...
    }

    2. 配置管理

    // config.js
    export const config = {
        api: {
            baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
            timeout: 10000
        },
        features: {
            enableNotifications: true,
            enableAnalytics: import.meta.env.PROD
        },
        limits: {
            maxFileSize: 10 * 1024 * 1024, // 10MB
            maxUploadsPerDay: 100
        }
    };
    
    // 使用
    import { config } from './config';
    
    fetch(`${config.api.baseURL}/users`, {
        timeout: config.api.timeout
    });

    可访问性 (A11y)

    // ✅ 好的做法 - 语义化 HTML
    
    
    
    
    // ✅ 好的做法 - 焦点管理
    const handleEscape = (e) => {
        if (e.key === 'Escape') {
            closeModal();
            closeButton.focus();
        }
    };
    
    // ✅ 好的做法 - ARIA 角色
    

    标题

    内容

    测试

    // ✅ 好的做法 - 单元测试
    import { describe, it, expect } from 'vitest';
    import { calculateTotal } from './utils';
    
    describe('calculateTotal', () => {
        it('应该正确计算总价', () => {
            const items = [
                { price: 10, quantity: 2 },
                { price: 5, quantity: 3 }
            ];
            expect(calculateTotal(items)).toBe(35);
        });
    
        it('空数组应该返回 0', () => {
            expect(calculateTotal([])).toBe(0);
        });
    });
    
    // ✅ 好的做法 - 集成测试
    import { render, screen, fireEvent } from '@testing-library/react';
    import { Button } from './Button';
    
    it('应该触发点击事件', () => {
        const handleClick = vitest.fn();
        render();
    
        fireEvent.click(screen.getByText('点击'));
        expect(handleClick).toHaveBeenCalledTimes(1);
    });

    最佳实践总结