面试题答案
一键面试性能优化方法
- 任务调度
- 使用队列控制并发数:通过创建任务队列,限制同一时间执行的异步任务数量。例如,使用
async - queue
库,在处理大量文件读取或网络请求时,设定一个合理的并发数(如10个网络请求同时进行),避免因过多任务同时执行耗尽系统资源。代码示例:
- 使用队列控制并发数:通过创建任务队列,限制同一时间执行的异步任务数量。例如,使用
const Queue = require('async - queue');
const queue = new Queue((task, callback) => {
// 执行任务,如文件读取或网络请求
fs.readFile(task.filePath, 'utf8', (err, data) => {
if (err) {
callback(err);
} else {
callback(null, data);
}
});
}, 10);
// 添加任务到队列
const tasks = [{ filePath: 'file1.txt' }, { filePath: 'file2.txt' }];
tasks.forEach(task => queue.push(task));
- **优先级调度**:对于不同类型的任务分配不同优先级。例如,重要的网络请求或关键业务逻辑相关的文件读取任务优先级较高。可以自定义一个优先级队列,按照优先级顺序执行任务。
2. 资源管理
- 连接池:在网络请求场景下,使用连接池管理网络连接。例如,对于数据库连接,可以使用 mysql2
库的连接池功能。这样可以避免频繁创建和销毁连接带来的开销。示例代码:
const mysql = require('mysql2');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
database: 'test',
connectionLimit: 10
});
pool.query('SELECT * FROM users', (err, results) => {
if (err) {
console.error(err);
} else {
console.log(results);
}
});
- **文件描述符管理**:在进行大量文件读取时,合理管理文件描述符。避免同时打开过多文件,导致系统资源耗尽。可以在读取完文件后及时关闭文件描述符。例如:
const fs = require('fs');
const fd = fs.openSync('test.txt', 'r');
try {
const data = fs.readFileSync(fd, 'utf8');
console.log(data);
} finally {
fs.closeSync(fd);
}
- 内存优化
- 及时释放内存:在异步任务完成后,及时释放不再使用的变量和对象所占用的内存。例如,在处理完一个大文件的读取后,将存储文件内容的变量设置为
null
,让垃圾回收机制回收内存。
- 及时释放内存:在异步任务完成后,及时释放不再使用的变量和对象所占用的内存。例如,在处理完一个大文件的读取后,将存储文件内容的变量设置为
let bigFileData;
fs.readFile('bigFile.txt', 'utf8', (err, data) => {
if (!err) {
bigFileData = data;
// 处理数据
bigFileData = null; // 处理完后释放内存
}
});
- **流处理**:对于大文件读取,使用流(Stream)的方式处理。流可以逐块读取文件,而不是一次性将整个文件读入内存。例如:
const fs = require('fs');
const readableStream = fs.createReadStream('bigFile.txt', 'utf8');
readableStream.on('data', (chunk) => {
// 处理每一块数据
console.log(chunk.length);
});
readableStream.on('end', () => {
console.log('读取完毕');
});
可能遇到的坑及解决方案
- 回调地狱
- 坑:在多层嵌套的异步回调中,代码变得难以阅读和维护,例如:
fs.readFile('file1.txt', 'utf8', (err1, data1) => {
if (err1) {
return console.error(err1);
}
fs.readFile('file2.txt', 'utf8', (err2, data2) => {
if (err2) {
return console.error(err2);
}
// 继续嵌套...
});
});
- **解决方案**:使用Promise、async/await 来处理异步操作。Promise 可以将回调式的异步操作转换为链式调用,提高代码可读性。例如:
const fs = require('fs').promises;
fs.readFile('file1.txt', 'utf8')
.then(data1 => fs.readFile('file2.txt', 'utf8'))
.then(data2 => {
// 处理 data2
})
.catch(err => console.error(err));
使用 `async/await` 语法糖,代码更接近同步写法:
const fs = require('fs').promises;
async function readFiles() {
try {
const data1 = await fs.readFile('file1.txt', 'utf8');
const data2 = await fs.readFile('file2.txt', 'utf8');
// 处理 data2
} catch (err) {
console.error(err);
}
}
readFiles();
- 内存泄漏
- 坑:如果在异步任务中没有及时释放不再使用的资源,会导致内存泄漏。例如,在事件监听中没有移除监听器,使得回调函数一直持有对某些对象的引用,无法被垃圾回收。
const emitter = new events.EventEmitter();
function listener() {
// 一些操作
}
emitter.on('event', listener);
// 后续没有移除监听器
- **解决方案**:在不需要监听事件时,及时移除监听器。例如:
const emitter = new events.EventEmitter();
function listener() {
// 一些操作
}
emitter.on('event', listener);
// 移除监听器
emitter.off('event', listener);
- 资源竞争
- 坑:多个异步任务同时访问和修改共享资源时,可能导致数据不一致。例如,多个异步任务同时写入同一个文件,可能造成文件内容损坏。
- 解决方案:使用锁机制来控制对共享资源的访问。例如,在文件写入场景下,可以使用
fs.writeFileSync
结合文件锁(如fs.flock
)来确保同一时间只有一个任务能写入文件。示例代码(简化示意):
const fs = require('fs');
const fd = fs.openSync('sharedFile.txt', 'w');
try {
fs.flockSync(fd, fs.constants.LOCK_EX);
fs.writeFileSync(fd, 'data to write');
} finally {
fs.flockSync(fd, fs.constants.LOCK_UN);
fs.closeSync(fd);
}