代码优化技巧
在Node.js开发中,代码优化是提升性能、可维护性和可扩展性的关键。从减少冗余操作到合理利用异步特性,优化手段多种多样。以下是一些实用技巧和具体示例。
减少同步操作
Node.js的核心优势在于非阻塞I/O,但同步操作会破坏这一特性。例如,fs.readFileSync
会阻塞事件循环:
// 避免
const data = fs.readFileSync('file.txt');
// 推荐
fs.readFile('file.txt', (err, data) => {
// 处理数据
});
对于必须使用同步操作的场景,可以通过worker_threads
隔离:
const { Worker } = require('worker_threads');
new Worker(`
const fs = require('fs');
const data = fs.readFileSync('large-file.json');
parentPort.postMessage(data.length);
`, { eval: true });
合理使用流处理
大文件操作时,流式处理能显著降低内存消耗:
// 传统方式(内存可能溢出)
fs.readFile('huge.log', (err, data) => {
const lines = data.toString().split('\n');
});
// 流式处理
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('huge.log')
});
rl.on('line', (line) => {
// 逐行处理
});
HTTP响应中也应优先使用流:
// 低效方式
app.get('/video', (req, res) => {
const video = fs.readFileSync('movie.mp4');
res.send(video);
});
// 高效方式
app.get('/video', (req, res) => {
fs.createReadStream('movie.mp4').pipe(res);
});
缓存策略优化
利用内存缓存减少重复计算:
const cache = new Map();
function heavyCompute(key) {
if (cache.has(key)) return cache.get(key);
const result = /* 复杂计算 */;
cache.set(key, result);
return result;
}
对于长期缓存,可结合LRU算法:
const LRU = require('lru-cache');
const cache = new LRU({ max: 1000 });
app.get('/data/:id', (req, res) => {
const cached = cache.get(req.params.id);
if (cached) return res.json(cached);
db.query('SELECT * FROM data WHERE id=?', [req.params.id], (result) => {
cache.set(req.params.id, result);
res.json(result);
});
});
避免内存泄漏
闭包使用不当会导致内存泄漏:
// 问题示例
function createLeak() {
const hugeArray = new Array(1e6).fill('*');
return function() {
console.log('Leak!');
// hugeArray 被永久持有
};
}
定时器需及时清理:
// 错误示范
setInterval(() => {
// 长期运行的任务
}, 1000);
// 正确做法
const timer = setInterval(/* ... */);
process.on('SIGTERM', () => clearInterval(timer));
异步控制优化
避免回调地狱,优先使用Promise/async:
// 回调地狱示例
fs.readFile('a.txt', (err, a) => {
fs.readFile('b.txt', (err, b) => {
fs.writeFile('c.txt', a + b, () => {
// 更多嵌套...
});
});
});
// 改进方案
(async () => {
const a = await fs.promises.readFile('a.txt');
const b = await fs.promises.readFile('b.txt');
await fs.promises.writeFile('c.txt', Buffer.concat([a, b]));
})();
批量操作使用Promise.all
:
// 顺序执行(慢)
for (const id of ids) {
await db.query('DELETE FROM items WHERE id=?', [id]);
}
// 并行执行(快)
await Promise.all(ids.map(id =>
db.query('DELETE FROM items WHERE id=?', [id])
));
性能关键路径优化
热点代码应避免不必要的操作:
// 低效字符串拼接
let html = '';
for (const item of items) {
html += `<li>${item.name}</li>`;
}
// 高效方式
const html = items.map(item => `<li>${item.name}</li>`).join('');
正则表达式预编译:
// 每次重新编译
function testString(str) {
return /^[a-z0-9]+$/.test(str);
}
// 预编译优化
const ALPHA_NUM = /^[a-z0-9]+$/;
function testString(str) {
return ALPHA_NUM.test(str);
}
错误处理优化
异步错误需特殊处理:
// 未捕获的Promise错误
app.get('/api', async (req, res) => {
const data = await fetchData(); // 可能reject
res.json(data);
});
// 正确方式
app.get('/api', async (req, res, next) => {
try {
const data = await fetchData();
res.json(data);
} catch (err) {
next(err);
}
});
使用domain处理复杂场景:
const domain = require('domain');
const d = domain.create();
d.on('error', (err) => {
console.error('Domain caught:', err);
});
d.run(() => {
// 在此执行可能出错的代码
process.nextTick(() => {
throw new Error('异步错误');
});
});
模块加载优化
动态加载非必要模块:
// 启动时立即加载
const PDF = require('pdfkit');
// 按需加载
async function generatePDF() {
const { default: PDF } = await import('pdfkit');
// 使用模块
}
模块缓存机制利用:
// 重复加载实际获取的是缓存
const mod1 = require('./utils');
const mod2 = require('./utils');
console.log(mod1 === mod2); // true
事件发射器优化
高频率事件需节流:
const EventEmitter = require('events');
const emitter = new EventEmitter();
// 原始高频事件
socket.on('data', (chunk) => {
emitter.emit('chunk', chunk);
});
// 优化方案
let buffer = [];
setInterval(() => {
if (buffer.length) {
emitter.emit('batch', buffer);
buffer = [];
}
}, 100);
socket.on('data', (chunk) => {
buffer.push(chunk);
});
进程管理优化
合理使用cluster模块:
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
// 根据CPU核心数fork进程
for (let i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
} else {
// 工作进程代码
require('./server');
}
子进程通信优化:
// 父进程
const { fork } = require('child_process');
const child = fork('compute.js');
// 使用message代替stdin/stdout
child.on('message', (result) => {
console.log('Result:', result);
});
// 子进程compute.js
process.on('message', (data) => {
const result = heavyCompute(data);
process.send(result);
});
数据库操作优化
批量插入代替循环插入:
// 低效方式
for (const user of users) {
await db.query('INSERT INTO users VALUES (?, ?)', [user.id, user.name]);
}
// 高效批量插入
const values = users.map(u => [u.id, u.name]);
await db.query(
'INSERT INTO users VALUES ?',
[values]
);
连接池合理配置:
const pool = mysql.createPool({
connectionLimit: 10, // 根据负载测试调整
acquireTimeout: 10000,
waitForConnections: true
});
// 使用后及时释放
app.get('/data', async (req, res) => {
const conn = await pool.getConnection();
try {
const [rows] = await conn.query('SELECT * FROM large_table');
res.json(rows);
} finally {
conn.release();
}
});
日志记录优化
避免同步日志写入:
// 问题代码
fs.appendFileSync('app.log', `${new Date()} - ${message}\n`);
// 优化方案
const stream = fs.createWriteStream('app.log', { flags: 'a' });
function log(message) {
stream.write(`${new Date().toISOString()} - ${message}\n`);
}
// 错误处理
stream.on('error', (err) => {
console.error('日志写入失败:', err);
});
结构化日志更利于分析:
const { createLogger, transports, format } = require('winston');
const logger = createLogger({
format: format.combine(
format.timestamp(),
format.json()
),
transports: [new transports.File({ filename: 'combined.log' })]
});
logger.info('用户登录', {
userId: 123,
ip: '192.168.1.1'
});
测试环境优化
区分环境配置:
// config.js
module.exports = {
db: process.env.NODE_ENV === 'production' ?
'mongodb://prod-db' :
'mongodb://localhost/test'
};
// 启动时指定环境
// NODE_ENV=production node app.js
Mock测试优化:
// 原始测试
test('fetch data', async () => {
const realData = await fetchFromAPI(); // 实际网络请求
expect(realData).toHaveProperty('id');
});
// 使用nock模拟
const nock = require('nock');
test('fetch data', async () => {
nock('https://api.example.com')
.get('/data')
.reply(200, { id: 123 });
const data = await fetchFromAPI();
expect(data).toEqual({ id: 123 });
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn