Node.js 面试题

1. 什么是 Node.js,它的特点有哪些?

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,让 JavaScript 可以在服务器端运行。它的特点如下:

  • 单线程、异步 I/O:采用单线程的事件循环机制处理异步 I/O 操作,避免了多线程编程中的锁和状态同步问题,能高效处理大量并发连接。例如在处理大量的网络请求时,不会因为某个请求的 I/O 操作而阻塞后续请求的处理。
  • 事件驱动:基于事件驱动的编程模型,通过事件触发相应的回调函数来处理任务。比如监听文件读取完成事件,当文件读取完成后触发回调函数进行后续处理。
  • 单线程但高效:虽然是单线程,但借助异步 I/O 和事件驱动,能充分利用 CPU 资源,在高并发场景下表现出色。
  • 跨平台:可以在 Windows、Linux、Mac OS 等多种操作系统上运行。
  • 丰富的模块系统:拥有庞大的 npm(Node Package Manager)生态系统,开发者可以方便地使用和分享各种开源模块。

2. 解释 Node.js 中的事件循环(Event Loop)

事件循环是 Node.js 实现异步编程的核心机制。它的工作流程如下:

  • 事件队列:Node.js 中的异步操作(如文件读取、网络请求等)会将任务放入事件队列中。
  • 循环处理:事件循环不断地从事件队列中取出任务并执行。它会依次处理不同阶段的任务,主要包括定时器检查、I/O 回调处理、空闲和准备阶段、轮询阶段、检查阶段和关闭阶段。
  • 异步回调:当异步操作完成后,对应的回调函数会被放入事件队列,等待事件循环处理。

例如,以下代码展示了一个简单的异步操作和事件循环的工作:

console.log('Start');

setTimeout(() => {
    console.log('Timeout callback');
}, 1000);

console.log('End');

在上述代码中,setTimeout 是一个异步操作,它的回调函数会在 1 秒后被放入事件队列。事件循环会继续执行后续代码,打印 'End',然后在合适的时机处理 setTimeout 的回调函数。

3. Node.js 中的模块有哪些类型?

  • 核心模块:由 Node.js 官方提供,如 fs(文件系统模块)、http(HTTP 模块)、path(路径处理模块)等。这些模块可以直接使用,无需安装。例如:
const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
});
  • 文件模块:开发者自己编写的 JavaScript 文件,通过 require 函数引入。例如,有一个 utils.js 文件:
// utils.js
exports.add = function(a, b) {
    return a + b;
};

在另一个文件中引入并使用:

const utils = require('./utils');
console.log(utils.add(1, 2));
  • 第三方模块:通过 npm 安装的模块,如 expresslodash 等。使用前需要先安装,然后通过 require 引入。例如:
npm install express
const express = require('express');
const app = express();

4. 如何处理 Node.js 中的错误?

  • 同步代码错误:使用 try...catch 语句捕获错误。例如:
try {
    const result = JSON.parse('invalid json');
} catch (error) {
    console.error('Error parsing JSON:', error.message);
}
  • 异步代码错误
    • 回调函数中的错误处理:在回调函数的第一个参数传递错误对象。例如:
const fs = require('fs');

fs.readFile('nonexistent.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('Error reading file:', err.message);
        return;
    }
    console.log(data);
});
- **Promise 中的错误处理**:使用 `.catch()` 方法捕获错误。例如:
function readFilePromise() {
    return new Promise((resolve, reject) => {
        fs.readFile('nonexistent.txt', 'utf8', (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

readFilePromise()
  .then(data => {
        console.log(data);
    })
  .catch(error => {
        console.error('Error reading file:', error.message);
    });
- **async/await 中的错误处理**:使用 `try...catch` 语句捕获错误。例如:
async function readFileAsync() {
    try {
        const data = await readFilePromise();
        console.log(data);
    } catch (error) {
        console.error('Error reading file:', error.message);
    }
}

readFileAsync();

5. 简述 Node.js 中的 Stream(流)

Stream 是 Node.js 中处理流式数据的抽象接口,它允许你以流式的方式处理数据,而不是一次性将所有数据加载到内存中。常见的流类型有:

  • 可读流(Readable Stream):用于读取数据,如文件读取、网络响应等。例如:
const fs = require('fs');
const readStream = fs.createReadStream('example.txt', 'utf8');

readStream.on('data', (chunk) => {
    console.log('Received chunk:', chunk);
});

readStream.on('end', () => {
    console.log('End of stream');
});

readStream.on('error', (err) => {
    console.error('Error reading stream:', err.message);
});
  • 可写流(Writable Stream):用于写入数据,如文件写入、网络请求发送等。例如:
const fs = require('fs');
const writeStream = fs.createWriteStream('output.txt', 'utf8');

writeStream.write('Hello, World!\n');
writeStream.end();

writeStream.on('finish', () => {
    console.log('Data written successfully');
});

writeStream.on('error', (err) => {
    console.error('Error writing stream:', err.message);
});
  • 双向流(Duplex Stream):既可以读取数据,也可以写入数据,如网络套接字。
  • 转换流(Transform Stream):在读取和写入数据的过程中对数据进行转换,如压缩、加密等。

6. 如何在 Node.js 中创建一个简单的 HTTP 服务器?

可以使用 http 模块创建一个简单的 HTTP 服务器。示例代码如下:

const http = require('http');

const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello, World!\n');
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

上述代码创建了一个 HTTP 服务器,当有客户端请求时,会返回一个包含 'Hello, World!' 的响应。

7. 什么是 Node.js 中的 Cluster 模块,它有什么作用?

Cluster 模块允许 Node.js 创建多个子进程(工作进程)来共享同一个服务器端口,从而充分利用多核 CPU 的性能。主要作用如下:

  • 提高性能:通过创建多个工作进程,每个工作进程可以处理独立的请求,从而提高服务器的并发处理能力。
  • 容错性:如果某个工作进程崩溃,其他工作进程仍然可以继续处理请求,保证服务器的可用性。

示例代码如下:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('exit', (worker, code, signal) => {
        console.log(`Worker ${worker.process.pid} died`);
    });
} else {
    http.createServer((req, res) => {
        res.writeHead(200);
        res.end('Hello, World!\n');
    }).listen(8000);

    console.log(`Worker ${process.pid} started`);
}

8. 解释 Node.js 中的 Buffer

Buffer 是 Node.js 中用于处理二进制数据的对象。在 Node.js 中,很多操作(如文件读取、网络通信等)都会涉及到二进制数据,Buffer 提供了一种高效的方式来处理这些数据。

可以通过多种方式创建 Buffer 对象,例如:

// 创建一个长度为 10 的 Buffer,初始值为 0
const buf1 = Buffer.alloc(10);

// 创建一个包含指定数据的 Buffer
const buf2 = Buffer.from('Hello, World!', 'utf8');

// 读取 Buffer 中的数据
console.log(buf2.toString('utf8'));

9. 如何在 Node.js 中进行文件操作?

可以使用 fs 模块进行文件操作,常见的操作包括文件读取、写入、删除等。

文件读取

const fs = require('fs');

// 同步读取文件
try {
    const data = fs.readFileSync('example.txt', 'utf8');
    console.log(data);
} catch (err) {
    console.error('Error reading file:', err.message);
}

// 异步读取文件
fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('Error reading file:', err.message);
        return;
    }
    console.log(data);
});

文件写入

const fs = require('fs');

// 同步写入文件
try {
    fs.writeFileSync('output.txt', 'Hello, World!', 'utf8');
    console.log('File written successfully');
} catch (err) {
    console.error('Error writing file:', err.message);
}

// 异步写入文件
fs.writeFile('output.txt', 'Hello, World!', 'utf8', (err) => {
    if (err) {
        console.error('Error writing file:', err.message);
        return;
    }
    console.log('File written successfully');
});

文件删除

const fs = require('fs');

fs.unlink('output.txt', (err) => {
    if (err) {
        console.error('Error deleting file:', err.message);
        return;
    }
    console.log('File deleted successfully');
});

10. 简述 Node.js 中的中间件(Middleware)

中间件是 Node.js 中用于处理 HTTP 请求和响应的函数,它可以在请求到达最终处理程序之前或响应发送给客户端之前对请求和响应进行处理。常见的用途包括日志记录、身份验证、错误处理等。

以 Express 框架为例,中间件的使用示例如下:

const express = require('express');
const app = express();

// 日志中间件
const logger = (req, res, next) => {
    console.log(`Received ${req.method} request for ${req.url}`);
    next();
};

app.use(logger);

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

在上述代码中,logger 是一个中间件函数,它会在每个请求到达时记录请求的方法和 URL,然后调用 next() 函数将请求传递给下一个中间件或路由处理程序。