Skip to content

Node.js

什么是 Node.js

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,使得 JavaScript 不仅能在浏览器中运行,也能在服务器端执行。
主要特点包括:

  • 事件驱动 & 非阻塞 I/O: 利用事件循环机制处理大量并发请求,适合 I/O 密集型应用。
  • 单线程异步模型: 使用单线程,但通过异步编程(回调函数、Promise、async/await)实现高性能。
  • 丰富的生态系统: 拥有庞大的 npm 包管理系统,能够快速引入各类模块。
  • 跨平台支持: 可在 Windows、Linux、macOS 等系统上运行。

事件循环(Event Loop)

事件循环是 Node.js 处理异步操作的核心机制。

基本流程:

  • 初始化阶段: 执行主线程中的所有同步代码。
  • 事件循环阶段: 循环检查任务队列(例如 I/O 操作、定时器、网络请求等)。
  • 任务处理: 一旦某个任务完成,相关回调被推入队列并依次执行。
  • 循环结束: 当队列为空,或程序调用 process.exit() 退出时,事件循环停止。

这种机制确保了即使在单线程环境下,也能高效处理大量异步任务。

回调函数(Callback)和回调地狱(Callback Hell)

回调函数(Callback)是一种在异步编程中常用的机制。

回调地狱(Callback Hell)是指在异步编程中,多次嵌套回调函数所形成的代码结构,导致代码可读性和可维护性变差的情况。

javascript
// 回调地狱示例
asyncFunction1(function(err, result1) {
    if (err) {
        handleError(err);
    } else {
        asyncFunction2(function(err, result2) {
            if (err) {
                handleError(err);
            } else {
                asyncFunction3(function(err, result3) {
                    if (err) {
                        handleError(err);
                    } else {
                        // 执行更多操作...
                    }
                });
            }
        });
    }
});

为了解决回调地狱的问题,可以采用以下几种方案:

  1. 使用 Promise:Promise 是一种更现代化的异步编程方法,它可以更清晰地表达异步操作的状态和结果。
  2. 使用 async/await:async/await 是建立在 Promise 之上的语法糖,它提供了一种更简洁、更易读的方式来编写异步代码。
  3. 使用控制流库:控制流库(如 async.js)提供了一些工具函数,可以简化异步代码的编写,并且避免回调地狱的问题。

模块系统(Module System)

Node.js 中的模块系统是一种用于组织和管理 JavaScript 代码的机制,它使得开发者可以将代码分割成独立的模块,并且在需要时进行加载和使用。

Node.js 使用 CommonJS 模块化系统,主要通过以下方式实现:

  • 模块导出: 使用 module.exports 将变量、函数或对象暴露给其他模块。
  • 模块引入: 使用 require('模块路径') 引入其他模块,从而实现模块间的通信。

这种方式帮助开发者将项目拆分成多个文件,提高代码的可维护性和复用性。

流(Stream)

流是一种处理大数据量或文件时的机制,可以分块传输数据而无需一次性加载全部内容到内存。

主要流类型有:

  • Readable(可读流): 用于读取数据,例如 fs.createReadStream()
  • Writable(可写流): 用于写入数据,例如 fs.createWriteStream()
  • Duplex(双工流): 同时具备读写能力,如 TCP 套接字。
  • Transform(转换流): 在数据读写过程中对数据进行转换,如压缩、加密等操作。

利用流可以有效降低内存占用,并提高数据处理效率。

缓冲区(Buffer)

Buffer 是 Node.js 中用来处理二进制数据的全局对象。

  • 用途: 在文件操作、网络通信等场景中,用于存储和处理原始的二进制数据。
  • 创建方法: 可以通过 Buffer.from()Buffer.alloc() 等方法创建。
  • 特点: 与字符串不同,Buffer 操作的是原始内存数据,适用于需要高性能数据处理的场景,如流媒体传输、加解密等。

如何在 Node.js 中处理异步操作

  1. 回调函数(Callbacks):回调函数是最基本的异步编程方式,在异步操作完成后执行回调函数(可能导致回调地狱)。

    javascript
    asyncFunction(arg1, arg2, function(err, result) {
        if (err) {
            // 处理错误
        } else {
            // 处理结果
        }
    });
  2. Promise:通过链式调用解决回调嵌套问题,使异步代码结构更加清晰。

    javascript
    asyncFunction(arg1, arg2)
        .then(result => {
            // 处理结果
        })
        .catch(err => {
            // 处理错误
        });
  3. async/await:基于 Promise 的语法糖,让异步代码看起来像同步代码,更易理解和维护。

    javascript
    async function someFunction() {
        try {
            let result = await asyncFunction(arg1, arg2);
            // 处理结果
        } catch (err) {
            // 处理错误
        }
    }
  4. 事件监听器(EventEmitter):Node.js 的核心模块 events 提供了事件驱动的编程方式。通过继承 EventEmitter 类并定义事件,可以实现异步操作的事件监听和触发。

    javascript
    const EventEmitter = require('events');
    
    class MyEmitter extends EventEmitter {}
    
    const myEmitter = new MyEmitter();
    
    myEmitter.on('event', function(data) {
        // 处理事件
    });
    
    // 在某个异步操作完成后触发事件
    asyncFunction(arg1, arg2, function(err, result) {
        if (!err) {
            myEmitter.emit('event', result);
        }
    });

Node.js 如何实现多进程处理?介绍 cluster 模块

虽然 Node.js 基于单线程模型,但可以通过以下方法实现并行处理:

  • cluster 模块: 允许创建多个子进程,每个子进程都是一个独立的 Node.js 实例,共享同一服务器端口,从而充分利用多核 CPU。
  • child_process 模块: 可以通过 spawnforkexec 方法创建子进程,用于执行命令或脚本。

利用 cluster 模块可以实现负载均衡,提升应用的并发处理能力。

如何在 Node.js 中进行错误处理?

  • 同步错误处理: 通过 try/catch 块捕获同步代码中抛出的异常。
  • 异步错误处理:
    • 回调方式: 错误通常作为第一个参数传递给回调函数。
    • Promise: 使用 .catch() 捕获异步操作中的错误。
    • async/await:async 函数中通过 try/catch 捕获错误。

此外,还可以使用全局错误处理机制(如 process.on('uncaughtException')process.on('unhandledRejection'))来捕捉未处理的异常,确保程序稳定性。

如何在 Node.js 中处理并发请求

  1. 使用异步编程模式:可以通过回调函数、Promise、async/await 等异步编程方式来处理并发请求。
  2. 使用事件驱动架构:通过监听和响应事件,可以有效地处理大量的并发请求,而无需创建多个线程或进程。
  3. 使用集群和负载均衡:可以使用集群和负载均衡技术来处理并发请求。通过将应用程序部署在多个节点上,并使用负载均衡器将请求分发到各个节点,可以提高应用程序的并发能力和可用性。
  4. 使用缓存:合理使用缓存可以减轻服务器的负载,提高并发请求的处理能力。可以使用内存缓存(如 Redis、Memcached 等)或者分布式缓存(如 Redis 集群、Redis Sentinel 等)来存储常用的数据,从而加速响应速度。
  5. 使用流处理数据:对于大量的并发请求,可以使用流来处理数据,逐块地读取和处理请求数据,而不是一次性加载所有数据到内存中。这样可以节省内存资源,并提高应用程序的性能。
  6. 使用限流和排队机制:为了防止服务器过载,可以使用限流和排队机制来控制并发请求的数量。例如可以使用令牌桶算法或漏桶算法来限制请求的速率,或者使用消息队列来排队并处理请求。
  7. 使用异步任务队列:可以使用异步任务队列来处理并发请求。将每个请求封装成一个异步任务,并将任务加入到任务队列中,然后由一组工作线程来处理任务,从而实现并发处理请求。

在 Node.js 中,如何实现文件的读写操作

在 Node.js 中,可以使用内置的 fs(文件系统)模块来实现文件的读写操作。

  1. 读取文件内容
  • fs.readFile(path[, options], callback): 异步地读取文件的全部内容。
  • fs.readFileSync(path[, options]): 同步地读取文件的全部内容。
  • fs.createReadStream(path[, options]): 创建一个可读流来读取文件的内容。

示例:

javascript
const fs = require('fs');

// 异步读取文件内容
fs.readFile('file.txt', 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
});

// 同步读取文件内容
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);

// 使用可读流读取文件内容
const readStream = fs.createReadStream('file.txt', 'utf8');
readStream.on('data', chunk => {
    console.log(chunk);
});
  1. 写入文件内容
  • fs.writeFile(file, data[, options], callback): 异步地将数据写入文件,如果文件已存在则覆盖。
  • fs.writeFileSync(file, data[, options]): 同步地将数据写入文件,如果文件已存在则覆盖。
  • fs.createWriteStream(path[, options]): 创建一个可写流来写入文件。

示例:

javascript
const fs = require('fs');

// 异步写入文件内容
fs.writeFile('file.txt', 'Hello, world!', 'utf8', err => {
    if (err) throw err;
    console.log('File written successfully');
});

// 同步写入文件内容
fs.writeFileSync('file.txt', 'Hello, world!', 'utf8');
console.log('File written successfully');

// 使用可写流写入文件内容
const writeStream = fs.createWriteStream('file.txt', 'utf8');
writeStream.write('Hello, world!');
writeStream.end();
  1. 其他常用文件操作
  • fs.rename(oldPath, newPath, callback): 异步地重命名文件或目录。
  • fs.unlink(path, callback): 异步地删除文件或符号链接。
  • fs.copyFile(src, dest[, flags], callback): 异步地将文件从一个路径复制到另一个路径。
  • fs.mkdir(path[, options], callback): 异步地创建目录。
  • fs.readdir(path[, options], callback): 异步地读取目录的内容。
  • fs.stat(path, callback): 异步地获取文件或目录的状态信息。

示例:

javascript
const fs = require('fs');

// 异步重命名文件
fs.rename('oldfile.txt', 'newfile.txt', err => {
    if (err) throw err;
    console.log('File renamed successfully');
});

// 异步删除文件
fs.unlink('file.txt', err => {
    if (err) throw err;
    console.log('File deleted successfully');
});

// 异步复制文件
fs.copyFile('src.txt', 'dest.txt', err => {
    if (err) throw err;
    console.log('File copied successfully');
});

// 异步创建目录
fs.mkdir('newdir', err => {
    if (err) throw err;
    console.log('Directory created successfully');
});

// 异步读取目录内容
fs.readdir('somedir', (err, files) => {
    if (err) throw err;
    console.log('Files in the directory:', files);
});

// 异步获取文件状态信息
fs.stat('file.txt', (err, stats) => {
    if (err) throw err;
    console.log('File stats:', stats);
});

Node.js 在内存管理和性能优化上有哪些常用的方法

常用的优化方法包括:

  • 非阻塞操作: 避免长时间的同步代码,充分利用异步 I/O 提高响应速度。
  • 代码优化: 优化算法和数据结构,减少不必要的资源消耗。
  • 使用缓存: 结合 Redis、Memcached 等内存缓存方案减少数据库压力。
  • 负载均衡: 采用 cluster 模块或反向代理(如 Nginx)分摊请求压力。
  • 内存泄露检测: 利用 Node.js 自带的 profiling 工具或第三方检测工具,及时发现并修复内存泄露问题。
  • 监控工具: 使用 PM2、New Relic 等监控工具实时追踪应用性能和资源使用情况。

Express/Koa中间件原理

中间件基于洋葱模型执行:

  • Express:通过 next() 控制权传递,回调函数线性执行
  • Koa:使用 async/await,中间件形成 Promise 链
js
// Express 中间件示例
app.use((req, res, next) => {
  console.log('1');
  next(); // 控制权传递给下一个中间件
  console.log('2');
})

// Koa 中间件示例
app.use(async (ctx, next) => {
  console.log('1');
  await next(); // 暂停当前中间件,执行下一个
  console.log('2');
});

Node.js 调试有哪些方法

常用工具

  1. console.log + debug模块(条件输出)
  2. node --inspect-brk app.js + Chrome DevTools
  3. VSCode 调试器(launch.json 配置)
  4. 日志工具:Winston/Bunyan 记录分级日志

require 的模块加载机制

缓存优先 -> 核心模块 -> 路径分析 -> 文件扩展名补全 -> 目录查找 (package.json/main)

Node.js 如何处理大文件上传

使用Stream 流处理避免内存溢出:

js
const fs = require('fs');
const http = require('http');

http.createServer((req, res) => {
  const writeStream = fs.createWriteStream('output.file');
  req.pipe(writeStream); // 直接管道写入文件
});

优化:使用busboy/formidable处理分块上传,前端配合分片上传。

如何保证 Node 服务高可用

  1. 进程管理:使用 PM2(pm2 start app.js -i max
  2. 负载均衡:Nginx 反向代理多节点
  3. 优雅退出
    js
    process.on('SIGTERM', () => {
      server.close(() => process.exit(0));
    });
  4. 健康检查:添加/health接口返回服务状态

进程间通信 (IPC) 如何实现

实现方式

js
// 主进程
const worker = cluster.fork();
worker.send('message');
worker.on('message', (msg) => {});

// 子进程
process.send('response');
process.on('message', (msg) => {});

应用场景:多进程共享状态、日志聚合、任务分发。

如何排查内存泄漏

  1. 使用--inspect启动 Node 进程
  2. Chrome DevTools -> Memory 标签抓取堆快照
  3. 对比多次快照,查看 Retained Size 增长的对象
  4. 使用heapdump模块生成堆转储文件分析 常见原因:全局变量引用、未清理的定时器、闭包滥用。

Released under the AGPL-3.0 License