代码优化
概述
代码优化是提升 Node.js 应用性能的基础手段。通过优化异步处理、缓存策略、数据库访问、算法复杂度等方面,可以显著提升应用的响应速度和吞吐量。本文档系统介绍各类代码优化技巧和最佳实践。
性能优化策略架构
code
┌─────────────────────────────────────────────────────────────────────┐
│ 代码性能优化策略 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│
│ │ 异步优化 │ │ 缓存优化 │ │ 数据库优化 │ │ 内存优化 ││
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤│
│ │• 并发控制 │ │• 内存缓存 │ │• 连接池 │ │• 对象池 ││
│ │• Promise池 │ │• Redis缓存 │ │• 批量操作 │ │• Stream ││
│ │• 队列处理 │ │• 多级缓存 │ │• 查询优化 │ │• Buffer ││
│ │• 错误处理 │ │• 缓存策略 │ │• 事务管理 │ │• GC优化 ││
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘│
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│
│ │ 算法优化 │ │ 数据结构 │ │ I/O优化 │ │ 网络优化 ││
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤│
│ │• 复杂度降低 │ │• Map/Set │ │• 文件流 │ │• 连接复用 ││
│ │• 循环优化 │ │• TypedArray│ │• 压缩传输 │ │• 批量请求 ││
│ │• 递归转迭代 │ │• 对象索引 │ │• 异步I/O │ │• DNS缓存 ││
│ │• 记忆化 │ │• WeakMap │ │• 缓冲策略 │ │• Keep-Alive││
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘│
│ │
└─────────────────────────────────────────────────────────────────────┘异步优化
并发控制模式
顺序执行 vs 并行执行
javascript
/**
* 异步执行模式对比
*/
// ❌ 顺序执行 - 总时间 = 所有任务时间之和
async function fetchUsersSequential(userIds) {
const users = [];
for (const id of userIds) {
const user = await fetchUser(id); // 串行等待
users.push(user);
}
return users;
}
// ✅ 并行执行 - 总时间 = 最慢任务的时间
async function fetchUsersParallel(userIds) {
return Promise.all(userIds.map(id => fetchUser(id)));
}
// ✅ 并发限制 - 平衡性能和资源消耗
async function fetchUsersWithLimit(userIds, limit = 5) {
const results = [];
for (let i = 0; i < userIds.length; i += limit) {
const batch = userIds.slice(i, i + limit);
const batchResults = await Promise.all(
batch.map(id => fetchUser(id))
);
results.push(...batchResults);
}
return results;
}
// 性能对比示例
async function performanceComparison() {
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.time('顺序执行');
await fetchUsersSequential(userIds);
console.timeEnd('顺序执行'); // ~5000ms (假设每个请求 500ms)
console.time('并行执行');
await fetchUsersParallel(userIds);
console.timeEnd('并行执行'); // ~500ms
console.time('并发限制(3)');
await fetchUsersWithLimit(userIds, 3);
console.timeEnd('并发限制(3)'); // ~2000ms
}使用 p-limit 控制并发
bash
npm install p-limitjavascript
const pLimit = require('p-limit');
/**
* p-limit 使用示例
*/
// 创建并发限制器(最多 5 个并发)
const limit = pLimit(5);
async function fetchUsers(userIds) {
// 将每个任务包装在 limit 中
const promises = userIds.map(id =>
limit(() => fetchUser(id))
);
return Promise.all(promises);
}
// 实际应用:批量处理文件
async function processFiles(files) {
const limit = pLimit(10); // 最多同时处理 10 个文件
const tasks = files.map(file =>
limit(async () => {
const content = await fs.promises.readFile(file, 'utf8');
return processContent(content);
})
);
return Promise.all(tasks);
}Promise 池实现
javascript
/**
* 自定义 Promise 池
* 更精细的并发控制
*/
class PromisePool {
/**
* @param {number} concurrency - 并发数量
*/
constructor(concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
/**
* 添加任务到池中
* @param {Function} task - 返回 Promise 的函数
* @returns {Promise} 任务执行结果
*/
async add(task) {
return new Promise((resolve, reject) => {
const run = async () => {
this.running++;
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.next();
}
};
if (this.running < this.concurrency) {
run();
} else {
this.queue.push(run);
}
});
}
/**
* 执行下一个任务
*/
next() {
if (this.queue.length > 0 && this.running < this.concurrency) {
const task = this.queue.shift();
task();
}
}
/**
* 批量执行任务
* @param {Array<Function>} tasks - 任务数组
* @returns {Promise<Array>} 所有任务结果
*/
async all(tasks) {
return Promise.all(tasks.map(task => this.add(task)));
}
}
// 使用示例
async function fetchAllUsers(userIds) {
const pool = new PromisePool(5); // 5 个并发
const tasks = userIds.map(id => () => fetchUser(id));
return pool.all(tasks);
}异步队列处理
javascript
/**
* 异步任务队列
* 支持优先级和重试
*/
class AsyncTaskQueue {
constructor(options = {}) {
this.concurrency = options.concurrency || 5;
this.maxRetries = options.maxRetries || 3;
this.retryDelay = options.retryDelay || 1000;
this.running = 0;
this.queue = [];
this.stats = {
completed: 0,
failed: 0,
retried: 0
};
}
/**
* 添加任务
* @param {Function} task - 任务函数
* @param {Object} options - 任务选项
*/
add(task, options = {}) {
const taskItem = {
task,
priority: options.priority || 0,
retries: 0,
resolve: null,
reject: null
};
return new Promise((resolve, reject) => {
taskItem.resolve = resolve;
taskItem.reject = reject;
this.queue.push(taskItem);
this.queue.sort((a, b) => b.priority - a.priority); // 按优先级排序
this.process();
});
}
/**
* 处理队列
*/
async process() {
while (this.running < this.concurrency && this.queue.length > 0) {
const taskItem = this.queue.shift();
this.executeTask(taskItem);
}
}
/**
* 执行任务
*/
async executeTask(taskItem) {
this.running++;
try {
const result = await taskItem.task();
this.stats.completed++;
taskItem.resolve(result);
} catch (error) {
if (taskItem.retries < this.maxRetries) {
taskItem.retries++;
this.stats.retried++;
// 延迟重试
setTimeout(() => {
this.queue.unshift(taskItem); // 优先重试
this.process();
}, this.retryDelay * taskItem.retries);
} else {
this.stats.failed++;
taskItem.reject(error);
}
} finally {
this.running--;
this.process();
}
}
/**
* 获取统计信息
*/
getStats() {
return { ...this.stats, running: this.running, queued: this.queue.length };
}
}
// 使用示例
const taskQueue = new AsyncTaskQueue({
concurrency: 3,
maxRetries: 3,
retryDelay: 1000
});
// 添加任务(支持优先级)
taskQueue.add(() => fetchUser(1), { priority: 10 }); // 高优先级
taskQueue.add(() => fetchUser(2), { priority: 1 }); // 低优先级异步错误处理
javascript
/**
* 异步错误处理最佳实践
*/
// ❌ 错误示例:未处理的 Promise
async function badExample() {
fetchData().then(data => processData(data));
// 如果 fetchData 或 processData 失败,错误会被忽略
}
// ✅ 正确示例:使用 try-catch
async function goodExample() {
try {
const data = await fetchData();
return processData(data);
} catch (error) {
console.error('处理失败:', error);
throw error; // 或返回默认值
}
}
// ✅ Promise 结果包装器
async function safeAsync(promise) {
try {
const data = await promise;
return { success: true, data, error: null };
} catch (error) {
return { success: false, data: null, error };
}
}
// 使用示例
const result = await safeAsync(fetchUser(1));
if (result.success) {
console.log('用户数据:', result.data);
} else {
console.error('获取失败:', result.error);
}
// ✅ 批量处理中的错误隔离
async function fetchAllUsersSafely(userIds) {
const results = await Promise.allSettled(
userIds.map(id => fetchUser(id))
);
return results.map((result, index) => ({
id: userIds[index],
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason : null
}));
}缓存优化
LRU 缓存实现
javascript
/**
* LRU (Least Recently Used) 缓存
* 自动淘汰最久未使用的数据
*/
class LRUCache {
constructor(maxSize = 100) {
this.maxSize = maxSize;
this.cache = new Map();
this.stats = {
hits: 0,
misses: 0
};
}
/**
* 获取缓存
*/
get(key) {
if (!this.cache.has(key)) {
this.stats.misses++;
return null;
}
// 移到末尾(最近使用)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
this.stats.hits++;
return value;
}
/**
* 设置缓存
*/
set(key, value) {
// 已存在则删除
if (this.cache.has(key)) {
this.cache.delete(key);
}
// 超过大小则删除最老的
else if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
/**
* 检查是否存在
*/
has(key) {
return this.cache.has(key);
}
/**
* 删除缓存
*/
delete(key) {
return this.cache.delete(key);
}
/**
* 清空缓存
*/
clear() {
this.cache.clear();
this.stats = { hits: 0, misses: 0 };
}
/**
* 获取命中率
*/
getHitRate() {
const total = this.stats.hits + this.stats.misses;
return total === 0 ? 0 : (this.stats.hits / total * 100).toFixed(2);
}
/**
* 获取缓存大小
*/
get size() {
return this.cache.size;
}
}
// 使用示例:API 响应缓存
class APICache {
constructor(ttl = 60000) {
this.cache = new LRUCache(1000);
this.ttl = ttl;
}
async get(key, fetchFn) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const data = await fetchFn();
this.cache.set(key, {
data,
timestamp: Date.now()
});
return data;
}
}
const apiCache = new APICache(60000); // 1 分钟过期
async function getUser(userId) {
return apiCache.get(`user:${userId}`, () =>
db.query('SELECT * FROM users WHERE id = ?', [userId])
);
}Redis 分布式缓存
bash
npm install ioredisjavascript
const Redis = require('ioredis');
/**
* Redis 缓存管理器
*/
class RedisCache {
constructor(options = {}) {
this.redis = new Redis(options.redis || {
host: 'localhost',
port: 6379
});
this.defaultTTL = options.defaultTTL || 3600;
this.prefix = options.prefix || 'cache:';
}
/**
* 获取缓存
*/
async get(key) {
const fullKey = this.prefix + key;
const cached = await this.redis.get(fullKey);
if (cached) {
return JSON.parse(cached);
}
return null;
}
/**
* 设置缓存
*/
async set(key, value, ttl = this.defaultTTL) {
const fullKey = this.prefix + key;
await this.redis.setex(fullKey, ttl, JSON.stringify(value));
}
/**
* 删除缓存
*/
async delete(key) {
const fullKey = this.prefix + key;
await this.redis.del(fullKey);
}
/**
* 批量删除(支持通配符)
*/
async deletePattern(pattern) {
const fullPattern = this.prefix + pattern;
const keys = await this.redis.keys(fullPattern);
if (keys.length > 0) {
await this.redis.del(keys);
}
}
/**
* 获取或设置(缓存穿透保护)
*/
async getOrSet(key, fetchFn, ttl = this.defaultTTL) {
const cached = await this.get(key);
if (cached !== null) {
return cached;
}
const data = await fetchFn();
// 防止缓存空值
if (data !== null && data !== undefined) {
await this.set(key, data, ttl);
}
return data;
}
}
// 使用示例
const redisCache = new RedisCache({
defaultTTL: 3600,
prefix: 'app:'
});
async function getUserWithCache(userId) {
return redisCache.getOrSet(
`user:${userId}`,
() => db.query('SELECT * FROM users WHERE id = ?', [userId]),
1800 // 30 分钟
);
}多级缓存架构
javascript
/**
* 多级缓存系统
* L1: 本地内存缓存(快速但容量有限)
* L2: Redis 缓存(分布式共享)
*/
class MultiLevelCache {
constructor(options = {}) {
// L1 本地缓存
this.l1Cache = new LRUCache(options.l1Size || 100);
// L2 Redis 缓存
this.l2Cache = new RedisCache(options.redis);
this.defaultTTL = options.defaultTTL || 3600;
this.stats = {
l1Hits: 0,
l2Hits: 0,
misses: 0
};
}
/**
* 获取数据
*/
async get(key) {
// 先查 L1
const l1Data = this.l1Cache.get(key);
if (l1Data !== null) {
this.stats.l1Hits++;
return l1Data;
}
// 再查 L2
const l2Data = await this.l2Cache.get(key);
if (l2Data !== null) {
this.stats.l2Hits++;
// 回填 L1
this.l1Cache.set(key, l2Data);
return l2Data;
}
this.stats.misses++;
return null;
}
/**
* 设置数据
*/
async set(key, value, ttl = this.defaultTTL) {
// 同时写入两级缓存
this.l1Cache.set(key, value);
await this.l2Cache.set(key, value, ttl);
}
/**
* 删除数据
*/
async delete(key) {
this.l1Cache.delete(key);
await this.l2Cache.delete(key);
}
/**
* 获取或设置
*/
async getOrSet(key, fetchFn, ttl = this.defaultTTL) {
const data = await this.get(key);
if (data !== null) {
return data;
}
const freshData = await fetchFn();
if (freshData !== null && freshData !== undefined) {
await this.set(key, freshData, ttl);
}
return freshData;
}
/**
* 获取缓存统计
*/
getStats() {
const total = this.stats.l1Hits + this.stats.l2Hits + this.stats.misses;
return {
...this.stats,
l1HitRate: total === 0 ? 0 : (this.stats.l1Hits / total * 100).toFixed(2) + '%',
l2HitRate: total === 0 ? 0 : (this.stats.l2Hits / total * 100).toFixed(2) + '%',
totalHitRate: total === 0 ? 0 : ((this.stats.l1Hits + this.stats.l2Hits) / total * 100).toFixed(2) + '%'
};
}
}缓存常见问题解决
javascript
/**
* 缓存穿透、雪崩、击穿解决方案
*/
// 1. 缓存穿透防护
class CachePenetrationGuard {
constructor(cache) {
this.cache = cache;
this.nullCache = new Set(); // 存储空值 key
this.nullCacheTTL = 60; // 空值缓存时间
}
async get(key, fetchFn) {
// 检查空值缓存
if (this.nullCache.has(key)) {
return null;
}
const data = await this.cache.get(key);
if (data !== null) {
return data;
}
const freshData = await fetchFn();
if (freshData === null || freshData === undefined) {
// 缓存空值,防止穿透
this.nullCache.add(key);
setTimeout(() => this.nullCache.delete(key), this.nullCacheTTL * 1000);
return null;
}
await this.cache.set(key, freshData);
return freshData;
}
}
// 2. 缓存雪崩防护
class CacheAvalancheGuard {
constructor(cache, baseTTL = 3600) {
this.cache = cache;
this.baseTTL = baseTTL;
}
/**
* 添加随机 TTL,避免同时过期
*/
async set(key, value) {
const randomTTL = this.baseTTL + Math.floor(Math.random() * 300);
await this.cache.set(key, value, randomTTL);
}
/**
* 缓存预热
*/
async warmup(keys, fetchFn) {
await Promise.all(keys.map(key =>
this.cache.getOrSet(key, () => fetchFn(key))
));
}
}
// 3. 缓存击穿防护(互斥锁)
class CacheBreakdownGuard {
constructor(cache) {
this.cache = cache;
this.locks = new Map();
}
async getOrSet(key, fetchFn, ttl) {
const data = await this.cache.get(key);
if (data !== null) {
return data;
}
// 获取锁
const lock = this.getLock(key);
try {
// 等待锁
await lock.acquire();
// 双重检查
const dataAgain = await this.cache.get(key);
if (dataAgain !== null) {
return dataAgain;
}
const freshData = await fetchFn();
await this.cache.set(key, freshData, ttl);
return freshData;
} finally {
lock.release();
}
}
getLock(key) {
if (!this.locks.has(key)) {
this.locks.set(key, new AsyncLock());
}
return this.locks.get(key);
}
}
/**
* 简单的异步锁
*/
class AsyncLock {
constructor() {
this.locked = false;
this.queue = [];
}
async acquire() {
while (this.locked) {
await new Promise(resolve => this.queue.push(resolve));
}
this.locked = true;
}
release() {
this.locked = false;
const next = this.queue.shift();
if (next) next();
}
}数据库优化
连接池配置
javascript
const mysql = require('mysql2/promise');
/**
* 数据库连接池配置
*/
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test',
// 连接池配置
connectionLimit: 20, // 最大连接数
waitForConnections: true, // 连接耗尽时等待
queueLimit: 0, // 等待队列长度(0 = 无限)
// 性能优化
acquireTimeout: 10000, // 获取连接超时
timeout: 60000, // 查询超时
timezone: '+08:00', // 时区设置
// 连接维护
idleTimeout: 60000, // 空闲连接超时
enableKeepAlive: true, // 保持连接活跃
keepAliveInitialDelay: 0
});
/**
* 连接池监控
*/
async function getPoolStats() {
return {
// mysql2 暂不支持直接获取连接池状态
// 可以通过自定义包装器实现
};
}
/**
* 优雅关闭
*/
async function closePool() {
await pool.end();
console.log('数据库连接池已关闭');
}
// 使用连接池
async function query(sql, params) {
const [rows] = await pool.execute(sql, params);
return rows;
}批量操作优化
javascript
/**
* 批量插入
*/
async function batchInsert(users) {
// ❌ 单条插入 - 低效
// for (const user of users) {
// await query('INSERT INTO users SET ?', user);
// }
// ✅ 批量插入 - 高效
const values = users.map(u => [u.name, u.email, u.age]);
const sql = 'INSERT INTO users (name, email, age) VALUES ?';
return query(sql, [values]);
}
/**
* 批量更新(使用 CASE WHEN)
*/
async function batchUpdate(updates) {
const cases = {};
const ids = [];
updates.forEach(({ id, field, value }) => {
if (!cases[field]) cases[field] = [];
cases[field].push(`WHEN ${id} THEN ?`);
ids.push(id);
});
const setClauses = Object.entries(cases)
.map(([field, whens]) =>
`${field} = CASE id ${whens.join(' ')} END`
)
.join(', ');
const sql = `
UPDATE users
SET ${setClauses}
WHERE id IN (?)
`;
return query(sql, [ids]);
}
/**
* 批量查询(避免 N+1)
*/
async function getUsersWithPosts() {
// ❌ N+1 查询
// const users = await query('SELECT * FROM users');
// for (const user of users) {
// user.posts = await query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
// }
// ✅ 优化方案 1:批量查询
const users = await query('SELECT id, name FROM users');
const userIds = users.map(u => u.id);
const posts = await query(
'SELECT user_id, title FROM posts WHERE user_id IN (?)',
[userIds]
);
// 组装数据
const postsByUser = {};
posts.forEach(post => {
if (!postsByUser[post.user_id]) postsByUser[post.user_id] = [];
postsByUser[post.user_id].push(post);
});
return users.map(user => ({
...user,
posts: postsByUser[user.id] || []
}));
// ✅ 优化方案 2:JOIN 查询
const results = await query(`
SELECT u.id, u.name, p.title
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
`);
// 结果需要去重合并
}查询优化技巧
javascript
/**
* 查询优化示例
*/
// 1. 只查询需要的字段
// ❌ 查询所有字段
// const users = await query('SELECT * FROM users WHERE age > 18');
// ✅ 只查询需要的字段
const users = await query('SELECT id, name FROM users WHERE age > 18');
// 2. 分页优化
// ❌ 大偏移量分页
// const users = await query('SELECT * FROM users LIMIT 10000, 20');
// ✅ 基于游标的分页
async function paginateWithCursor(lastId, limit = 20) {
return query(
'SELECT * FROM users WHERE id > ? ORDER BY id LIMIT ?',
[lastId, limit]
);
}
// ✅ 延迟关联(先查 ID,再查详情)
async function paginateWithDefer(offset, limit) {
const [ids] = await query(
'SELECT id FROM users LIMIT ?, ?',
[offset, limit]
);
return query(
'SELECT * FROM users WHERE id IN (?)',
[ids.map(i => i.id)]
);
}
// 3. 索引使用优化
// ✅ 使用覆盖索引
const users = await query(
'SELECT id, name FROM users WHERE age > 18 AND status = "active"'
);
// ✅ 避免索引失效
// ❌ 在索引列上使用函数
// WHERE YEAR(created_at) = 2023
// ✅ 使用范围查询
// WHERE created_at >= '2023-01-01' AND created_at < '2024-01-01'
// 4. 事务优化
async function transferMoney(fromId, toId, amount) {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
// 悲观锁
const [from] = await connection.execute(
'SELECT balance FROM accounts WHERE id = ? FOR UPDATE',
[fromId]
);
if (from[0].balance < amount) {
throw new Error('余额不足');
}
await connection.execute(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, fromId]
);
await connection.execute(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, toId]
);
await connection.commit();
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}JSON 处理优化
快速 JSON 序列化
bash
npm install fast-json-stringifyjavascript
const fastJson = require('fast-json-stringify');
/**
* 使用 JSON Schema 加速序列化
*/
// 定义 Schema
const userSchema = fastJson({
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' },
email: { type: 'string' },
age: { type: 'number' },
tags: {
type: 'array',
items: { type: 'string' }
}
}
});
// 性能对比
const user = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
age: 25,
tags: ['developer', 'nodejs']
};
// 原生 JSON.stringify
console.time('JSON.stringify');
for (let i = 0; i < 100000; i++) {
JSON.stringify(user);
}
console.timeEnd('JSON.stringify');
// fast-json-stringify(快 2-3 倍)
console.time('fast-json-stringify');
for (let i = 0; i < 100000; i++) {
userSchema(user);
}
console.timeEnd('fast-json-stringify');流式 JSON 解析
bash
npm install stream-jsonjavascript
const { chain } = require('stream-chain');
const { parser } = require('stream-json');
const { streamArray } = require('stream-json/streamers/StreamArray');
const fs = require('fs');
/**
* 处理大型 JSON 文件
*/
// ❌ 一次性加载 - 内存占用大
// const data = JSON.parse(fs.readFileSync('large.json', 'utf8'));
// ✅ 流式解析 - 内存占用小
async function processLargeJSON(filePath) {
const pipeline = chain([
fs.createReadStream(filePath),
parser(),
streamArray()
]);
let count = 0;
for await (const { key, value } of pipeline) {
// 逐条处理
await processItem(value);
count++;
// 批量提交
if (count % 1000 === 0) {
console.log(`已处理 ${count} 条记录`);
}
}
console.log(`处理完成,共 ${count} 条记录`);
}
/**
* 提取特定字段
*/
const { pick } = require('stream-json/filters/Pick');
async function extractFields(filePath) {
const pipeline = chain([
fs.createReadStream(filePath),
parser(),
pick({ filter: 'users' }),
streamArray()
]);
const users = [];
for await (const { value } of pipeline) {
users.push({
id: value.id,
name: value.name
});
}
return users;
}正则表达式优化
javascript
/**
* 正则表达式优化技巧
*/
// 1. 预编译正则表达式
// ❌ 每次调用都编译
function validateEmailBad(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// ✅ 预编译
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function validateEmail(email) {
return EMAIL_REGEX.test(email);
}
// 2. 正则缓存
const regexCache = new Map();
function getRegex(pattern, flags = '') {
const key = pattern + flags;
if (!regexCache.has(key)) {
regexCache.set(key, new RegExp(pattern, flags));
}
return regexCache.get(key);
}
// 3. 避免回溯灾难
// ❌ 可能导致灾难性回溯
const badRegex = /(a+)+$/;
// ✅ 使用原子组或占有量词
const goodRegex = /(a++)+$/; // 占有量词
// 4. 常用正则表达式库
const patterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone: /^1[3-9]\d{9}$/,
url: /^https?:\/\/[\w\-]+(\.[\w\-]+)+[/#?]?.*$/,
ip: /^(\d{1,3}\.){3}\d{1,}$/,
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
};
// 5. 性能测试
function testRegexPerformance(regex, str, iterations = 100000) {
console.time('Regex test');
for (let i = 0; i < iterations; i++) {
regex.test(str);
}
console.timeEnd('Regex test');
}函数优化
记忆化(Memoization)
javascript
/**
* 函数记忆化
* 缓存函数计算结果
*/
// 简单实现
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;
};
}
// 支持异步函数
function memoizeAsync(fn) {
const cache = new Map();
const pending = new Map();
return async function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
// 避免并发重复计算
if (pending.has(key)) {
return pending.get(key);
}
const promise = fn.apply(this, args);
pending.set(key, promise);
try {
const result = await promise;
cache.set(key, result);
return result;
} finally {
pending.delete(key);
}
};
}
// 使用示例:斐波那契数列
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.time('Fibonacci 40');
console.log(fibonacci(40)); // 瞬间完成
console.timeEnd('Fibonacci 40');防抖与节流
javascript
/**
* 防抖(Debounce)
* 延迟执行,重复调用重置计时器
*/
function debounce(fn, delay, immediate = false) {
let timer = null;
return function(...args) {
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) {
fn.apply(this, args);
}
}, delay);
if (callNow) {
fn.apply(this, args);
}
};
}
/**
* 节流(Throttle)
* 固定间隔执行
*/
function throttle(fn, interval, options = {}) {
let lastTime = 0;
let timer = null;
const { leading = true, trailing = true } = options;
return function(...args) {
const now = Date.now();
if (!lastTime && !leading) {
lastTime = now;
}
const remaining = interval - (now - lastTime);
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = now;
fn.apply(this, args);
} else if (!timer && trailing) {
timer = setTimeout(() => {
lastTime = leading ? Date.now() : 0;
timer = null;
fn.apply(this, args);
}, remaining);
}
};
}
// 使用示例
const searchDebounced = debounce(search, 300);
const scrollThrottled = throttle(handleScroll, 100);
// 搜索输入
input.addEventListener('input', searchDebounced);
// 滚动事件
window.addEventListener('scroll', scrollThrottled);惰性函数
javascript
/**
* 惰性函数
* 首次执行时确定最终实现
*/
function createXHR() {
if (typeof XMLHttpRequest !== 'undefined') {
return function() {
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject !== 'undefined') {
return function() {
return new ActiveXObject('Microsoft.XMLHTTP');
};
}
return function() {
throw new Error('XHR not supported');
};
}
const getXHR = createXHR();
// 首次调用后,后续调用直接使用确定的方法
const xhr = getXHR();
// 另一个示例:事件监听
function addEvent(element, type, handler) {
if (element.addEventListener) {
addEvent = function(element, type, handler) {
element.addEventListener(type, handler, false);
};
} else if (element.attachEvent) {
addEvent = function(element, type, handler) {
element.attachEvent('on' + type, handler);
};
}
addEvent(element, type, handler);
}数组与对象优化
数组操作优化
javascript
/**
* 数组操作性能优化
*/
// 1. 避免在循环中修改数组
// ❌ 正序删除会跳过元素
// for (let i = 0; i < arr.length; i++) {
// if (condition) arr.splice(i, 1);
// }
// ✅ 倒序删除
for (let i = arr.length - 1; i >= 0; i--) {
if (condition) arr.splice(i, 1);
}
// ✅ 使用 filter 创建新数组
const filtered = arr.filter(item => !condition);
// 2. 数组查找优化
// ❌ O(n) 复杂度
const found = arr.includes(target);
const index = arr.findIndex(item => item.id === targetId);
// ✅ O(1) 复杂度 - 使用 Set/Map
const set = new Set(arr);
const found = set.has(target);
const map = new Map(arr.map(item => [item.id, item]));
const item = map.get(targetId);
// 3. 数组去重
// ❌ 性能较差
const unique = arr.filter((item, index) => arr.indexOf(item) === index);
// ✅ 使用 Set
const unique = [...new Set(arr)];
// 4. 大数组排序
// ❌ 默认排序(字符串比较)
arr.sort();
// ✅ 数值排序
arr.sort((a, b) => a - b);
// 5. 避免使用 delete 删除数组元素
// ❌ 使用 delete(留下空洞)
// delete arr[2]; // [1, 2, empty, 4, 5]
// ✅ 使用 splice
arr.splice(2, 1); // [1, 2, 4, 5]TypedArray 优化
javascript
/**
* TypedArray - 类型化数组
* 用于处理大量数值数据,性能更好
*/
// 普通数组
const normalArray = new Array(1000000);
for (let i = 0; i < 1000000; i++) {
normalArray[i] = i;
}
// TypedArray(内存占用更小,操作更快)
const typedArray = new Float64Array(1000000);
for (let i = 0; i < 1000000; i++) {
typedArray[i] = i;
}
// TypedArray 类型
const types = {
Int8Array: '8位有符号整数',
Uint8Array: '8位无符号整数',
Int16Array: '16位有符号整数',
Uint16Array: '16位无符号整数',
Int32Array: '32位有符号整数',
Uint32Array: '32位无符号整数',
Float32Array: '32位浮点数',
Float64Array: '64位浮点数'
};
// 使用示例:图像处理
function processImageData(width, height) {
// RGBA 每像素 4 字节
const pixels = new Uint8ClampedArray(width * height * 4);
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 255; // R
pixels[i + 1] = 0; // G
pixels[i + 2] = 0; // B
pixels[i + 3] = 255; // A
}
return pixels;
}对象优化
javascript
/**
* 对象操作优化
*/
// 1. 避免频繁添加/删除属性
// ❌ 动态添加属性(隐藏类变化,性能下降)
function Point(x, y) {
this.x = x;
if (y) this.y = y; // 条件添加
}
// ✅ 保持对象结构一致
function Point(x, y) {
this.x = x;
this.y = y || 0; // 总是初始化
}
// 2. 使用 Object.create(null) 创建纯净对象
const cleanObj = Object.create(null);
cleanObj.key = 'value';
// 没有 prototype,更省内存
// 3. 属性访问优化
// ❌ 动态属性名
obj[dynamicKey] = value;
// ✅ 静态属性名
obj.staticKey = value;
// 4. 对象克隆
// 浅拷贝
const shallow = { ...obj };
const shallow2 = Object.assign({}, obj);
// 深拷贝
const deep = JSON.parse(JSON.stringify(obj));
// 或使用 structuredClone(Node.js 17+)
const deep2 = structuredClone(obj);字符串优化
字符串拼接
javascript
/**
* 字符串拼接优化
*/
// ❌ 大量字符串使用 + 拼接
let str = '';
for (let i = 0; i < 10000; i++) {
str += 'a';
}
// ✅ 使用数组 join
const parts = [];
for (let i = 0; i < 10000; i++) {
parts.push('a');
}
const str = parts.join('');
// ✅ 使用 repeat(重复相同字符)
const str = 'a'.repeat(10000);
// ✅ 使用模板字符串(可读性好,性能相近)
const message = `Hello ${name}, you have ${count} messages`;字符串查找
javascript
/**
* 字符串查找优化
*/
const text = 'Hello World, this is a test string';
// 1. 是否包含子串
// ✅ includes(ES6,语义清晰)
const hasWorld = text.includes('World');
// 2. 查找位置
const index = text.indexOf('World');
// 3. 开头/结尾匹配
// ✅ startsWith / endsWith(ES6)
const startsWithHello = text.startsWith('Hello');
const endsWithTest = text.endsWith('test');
// 4. 批量替换
const replaced = text.replace(/Hello|World/g, match =>
match.toUpperCase()
);性能测试工具
Benchmark.js
bash
npm install benchmarkjavascript
const Benchmark = require('benchmark');
/**
* 使用 Benchmark.js 进行基准测试
*/
const suite = new Benchmark.Suite();
// 测试对象
const arr = new Array(10000).fill(0).map((_, i) => i);
suite
.add('Array#forEach', function() {
let sum = 0;
arr.forEach(item => sum += item);
})
.add('for loop', function() {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
})
.add('for...of', function() {
let sum = 0;
for (const item of arr) {
sum += item;
}
})
.add('reduce', function() {
arr.reduce((sum, item) => sum + item, 0);
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('最快的是: ' + this.filter('fastest').map('name'));
})
.run({ async: true });压力测试
bash
# 使用 autocannon 进行压力测试
npm install -g autocannon
# 基本用法
autocannon -c 100 -d 30 http://localhost:3000
# 参数说明
# -c, --connections: 并发连接数
# -d, --duration: 测试持续时间(秒)
# -p, --pipelining: 管道请求数
# -m, --method: HTTP 方法
# -H, --header: 请求头
# -b, --body: 请求体javascript
// 编程方式使用 autocannon
const autocannon = require('autocannon');
async function runBenchmark() {
const result = await autocannon({
url: 'http://localhost:3000',
connections: 100,
duration: 30,
requests: [
{
method: 'GET',
path: '/api/users'
},
{
method: 'POST',
path: '/api/users',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Test' })
}
]
});
console.log('测试结果:', {
总请求数: result.requests.total,
平均延迟: result.latency.average + 'ms',
P99延迟: result.latency.p99 + 'ms',
吞吐量: result.throughput.average + ' bytes/sec'
});
}clinic.js 诊断
bash
# 安装 clinic.js
npm install -g clinic
# Doctor - 诊断 I/O 问题
clinic doctor -- node app.js
# Bubbleprof - 分析异步操作
clinic bubbleprof -- node app.js
# Flame - 火焰图分析 CPU
clinic flame -- node app.js
# Heapprofiler - 堆内存分析
clinic heapprofiler -- node app.js常见问题解答
Q1: 何时使用并行处理,何时使用顺序处理?
A: 根据任务特性选择:
| 任务类型 | 推荐方式 | 原因 |
|---|---|---|
| 独立的 I/O 操作 | 并行 | I/O 等待时间可重叠 |
| 有依赖关系的操作 | 顺序 | 保证执行顺序 |
| 计算密集型 | 并发限制 | 避免耗尽 CPU |
| 批量数据库查询 | 并行+限制 | 提高性能,避免数据库压力 |
Q2: 缓存应该设置多大的 TTL?
A: 根据数据特性设置:
javascript
// 热点数据:短 TTL,高频率更新
const hotDataTTL = 60; // 1 分钟
// 稳定数据:长 TTL
const stableDataTTL = 86400; // 1 天
// 用户相关:中等 TTL
const userDataTTL = 1800; // 30 分钟
// 配置数据:很长 TTL,手动失效
const configTTL = 604800; // 1 周Q3: 如何选择合适的数据结构?
A: 根据操作特点选择:
| 需求 | 推荐数据结构 | 时间复杂度 |
|---|---|---|
| 快速查找 | Map / Set | O(1) |
| 有序数据 | 数组 + 排序 | O(log n) |
| 频繁插入删除 | 链表(数组 splice) | O(n) |
| 去重 | Set | O(1) |
| 键值对 | Object / Map | O(1) |
| 大量数值 | TypedArray | 内存优化 |
Q4: 如何避免内存泄漏?
A: 注意以下场景:
- 全局变量:避免在全局存储数据
- 闭包:注意大对象的引用
- 事件监听器:及时移除不需要的监听器
- 定时器:及时清除定时器
- 缓存:设置合理的容量限制和过期策略
Q5: 如何评估优化效果?
A: 使用以下方法:
- 基准测试:使用 Benchmark.js 对比优化前后
- 压力测试:使用 autocannon 测试吞吐量和延迟
- 内存分析:使用 clinic.js heapprofiler 分析内存
- CPU 分析:使用 clinic.js flame 分析 CPU 热点
- 监控数据:对比生产环境的性能指标
最佳实践总结
优化优先级
code
┌─────────────────────────────────────────────────────────────────┐
│ 性能优化优先级金字塔 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ 算法优化 │ ← 影响最大 │
│ └────┬────┘ │
│ ┌─────┴─────┐ │
│ │ 架构优化 │ │
│ └─────┬─────┘ │
│ ┌────────┴────────┐ │
│ │ 数据库优化 │ │
│ └────────┬────────┘ │
│ ┌─────────────┴─────────────┐ │
│ │ 缓存优化 │ │
│ └─────────────┬─────────────┘ │
│ ┌──────────────────┴──────────────────┐ │
│ │ 异步并发优化 │ │
│ └──────────────────┬──────────────────┘ │
│ ┌───────────────────────┴───────────────────────┐ │
│ │ 代码细节优化 │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘优化检查清单
-
异步优化
- 使用 Promise.all 并行处理独立任务
- 控制并发数量避免资源耗尽
- 正确处理异步错误
-
缓存优化
- 使用多级缓存减少延迟
- 设置合理的 TTL 和容量
- 处理缓存穿透、雪崩、击穿
-
数据库优化
- 使用连接池管理连接
- 批量操作减少数据库交互
- 避免 N+1 查询问题
-
内存优化
- 使用 Stream 处理大数据
- 避免内存泄漏
- 合理使用 Buffer 和 TypedArray
-
代码质量
- 算法复杂度优化
- 使用高效的数据结构
- 避免重复计算(记忆化)
-
性能测试
- 基准测试对比优化效果
- 压力测试验证系统容量
- 持续监控生产环境性能