现在位置: 首页 > Node.js 教程 > 正文

Node.js 基础概念

Node.js 是一个基于 Chrome V8 JavaScript 引擎构建的 JavaScript 运行时环境。简单来说,Node.js 让 JavaScript 可以在服务器端运行,而不仅仅局限于浏览器中。

Node.js 的核心特点

1. 单线程事件循环

  • Node.js 使用单线程的事件循环模型
  • 通过事件驱动和回调函数处理并发请求
  • 避免了传统多线程编程中的线程切换开销

2. 非阻塞 I/O

  • 所有 I/O 操作(文件读写、网络请求等)都是异步的
  • 当等待 I/O 操作完成时,程序不会被阻塞,可以继续处理其他任务
  • 大大提高了应用程序的吞吐量

3. 跨平台

  • 支持 Windows、macOS、Linux 等多种操作系统
  • 一次编写,到处运行

4. 丰富的生态系统

  • npm(Node Package Manager)拥有数百万个开源包
  • 活跃的开发者社区

与传统服务器端技术的区别

1、传统服务器端技术(如 Apache + PHP):

请求1 → 创建线程1 → 处理请求 → 返回响应 → 销毁线程1
请求2 → 创建线程2 → 处理请求 → 返回响应 → 销毁线程2
请求3 → 创建线程3 → 处理请求 → 返回响应 → 销毁线程3

2、Node.js 的处理方式:

请求1 → 事件循环 → 处理请求 → 返回响应
请求2 → 事件循环 → 处理请求 → 返回响应  (复用同一线程)
请求3 → 事件循环 → 处理请求 → 返回响应

特性 传统多线程模型 Node.js 单线程模型
内存占用 每个线程占用 2MB 左右 单线程,内存占用少
并发处理 线程数量限制并发数 事件循环处理高并发
上下文切换 频繁的线程切换开销 无线程切换开销
编程复杂度 需要处理线程同步 避免了锁和线程同步问题
适用场景 CPU 密集型任务 I/O 密集型任务

Node.js 的应用场景

1、适合的应用场景

  1. Web 应用程序

    • RESTful API 服务
    • 单页应用(SPA)的后端服务
    • 实时 Web 应用
  2. 实时应用

    • 聊天应用
    • 在线游戏
    • 协作工具(如在线文档编辑)
  3. 微服务架构

    • 轻量级的微服务
    • API 网关
    • 服务间通信
  4. 工具和命令行应用

    • 构建工具(如 Webpack、Gulp)
    • 脚手架工具
    • 自动化脚本
  5. 物联网(IoT)应用

    • 设备数据收集
    • 传感器数据处理

2、不适合的应用场景

  1. CPU 密集型任务

    • 图像/视频处理
    • 复杂的数学计算
    • 大数据分析
  2. 需要大量计算的应用

    • 机器学习训练
    • 科学计算
    • 加密货币挖矿

事件驱动和非阻塞 I/O 模型

传统阻塞 I/O 模型示例:

实例

// 伪代码 - 阻塞式操作
const data1 = readFileSync('file1.txt');  // 等待文件读取完成
const data2 = readFileSync('file2.txt');  // 等待文件读取完成
const data3 = readFileSync('file3.txt');  // 等待文件读取完成
console.log('所有文件读取完成');

Node.js 非阻塞 I/O 模型示例:

实例

// Node.js 异步操作
const fs = require('fs');

fs.readFile('file1.txt', (err, data1) => {
    console.log('文件1读取完成');
});

fs.readFile('file2.txt', (err, data2) => {
    console.log('文件2读取完成');
});

fs.readFile('file3.txt', (err, data3) => {
    console.log('文件3读取完成');
});

console.log('程序继续执行,不等待文件读取');

事件循环机制:

事件循环是 Node.js 实现非阻塞 I/O 的核心机制:

  1. 调用栈(Call Stack):执行同步代码
  2. 事件队列(Event Queue):存储异步操作的回调函数
  3. 事件循环(Event Loop):监控调用栈和事件队列
┌───────────────────────────┐
┌─>│           timers          │  ← setTimeout, setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  ← I/O 回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  ← 内部使用
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │  ← 获取新的 I/O 事件
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  ← setImmediate 回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │  ← 关闭事件回调
   └───────────────────────────┘

JavaScript 运行时环境

V8 引擎简介

V8 是 Google 开发的高性能 JavaScript 引擎,也是 Chrome 浏览器的核心组件。

Node.js 使用 V8 引擎来执行 JavaScript 代码。

V8 引擎的特点:

  1. 即时编译(JIT)

    • 将 JavaScript 代码直接编译为机器码
    • 无需中间字节码,执行效率更高
  2. 垃圾收集

    • 自动内存管理
    • 使用分代垃圾收集算法
  3. 优化技术

    • 内联缓存(Inline Caching)
    • 隐藏类(Hidden Classes)
    • 动态优化

浏览器 JavaScript vs Node.js JavaScript

虽然都使用 JavaScript 语言,但运行环境的差异导致了一些重要区别:

相同点:

  • 都使用相同的 JavaScript 语法
  • 都支持 ES6+ 特性
  • 都使用 V8 引擎(Chrome 浏览器)

不同点:

特性 浏览器 JavaScript Node.js JavaScript
全局对象 window global
模块系统 ES6 modules, AMD CommonJS, ES6 modules
文件系统访问 不可访问 完全访问
网络请求 XMLHttpRequest, Fetch http, https 模块
DOM 操作 支持 不支持
进程控制 不支持 支持

浏览器环境示例:

实例

// 浏览器中的全局对象和 DOM 操作
console.log(window);  // 全局对象
document.getElementById('app');  // DOM 操作
localStorage.setItem('key', 'value');  // 本地存储

Node.js 环境示例:

实例

// Node.js 中的全局对象和文件操作
console.log(global);  // 全局对象
const fs = require('fs');  // 文件系统模块
fs.readFile('data.txt', 'utf8', callback);  // 文件操作

全局对象的差异

浏览器中的全局对象:

实例

// 浏览器环境
console.log(this === window);  // true
var globalVar = 'hello';
console.log(window.globalVar);  // 'hello'

Node.js 中的全局对象:

实例

// Node.js 环境
console.log(this === global);  // false (在模块中)
var globalVar = 'hello';
console.log(global.globalVar);  // undefined

// Node.js 中的模块作用域
console.log(this);  // {} (空对象)
console.log(module.exports === this);  // true

Node.js 特有的全局变量:

实例

console.log(__dirname);   // 当前模块的目录路径
console.log(__filename);  // 当前模块的文件路径
console.log(process);     // 进程对象
console.log(Buffer);      // 缓冲区构造函数

Node.js 的优势与局限

优势详解

1. 高并发处理能力

传统多线程服务器处理 10,000 个并发连接需要约 20GB 内存(每个线程2MB),而 Node.js 只需要很少的内存就能处理相同数量的连接。

实例

// Node.js 高并发示例
const http = require('http');

const server = http.createServer((req, res) => {
    // 模拟异步操作
    setTimeout(() => {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('Hello World\n');
    }, 100);
});

server.listen(3000, () => {
    console.log('服务器运行在 http://localhost:3000/');
});

// 这个服务器可以同时处理数千个请求而不会阻塞

2. 快速开发

统一的 JavaScript 语言栈使得前后端开发更加高效:

实例

// 同一份验证逻辑可以在前后端复用
function validateEmail(email) {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
}

// 前端使用
if (validateEmail(userInput)) {
    // 发送到服务器
}

// 后端使用
if (validateEmail(req.body.email)) {
    // 保存到数据库
}

3. 统一语言栈的优势

  • 代码复用:前后端可以共享工具函数、验证逻辑等
  • 团队效率:开发人员可以同时负责前后端开发
  • 技术栈简化:减少学习成本和技术复杂度

4. 丰富的 npm 生态系统

# npm 提供了数百万个包
npm search express     # 搜索包
npm install express    # 安装包
npm update            # 更新包

局限性分析

1. CPU 密集型任务的性能问题

实例

// 不好的例子:CPU 密集型任务会阻塞事件循环
function fibonacciSync(n) {
    if (n < 2) return n;
    return fibonacciSync(n - 1) + fibonacciSync(n - 2);
}

// 这会阻塞整个应用
console.log(fibonacciSync(40));  // 阻塞数秒

// 解决方案:使用 Worker Threads 或将任务分解
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
    // 主线程
    const worker = new Worker(__filename, {
        workerData: { n: 40 }
    });
   
    worker.on('message', (result) => {
        console.log('结果:', result);
    });
} else {
    // Worker 线程
    const result = fibonacciSync(workerData.n);
    parentPort.postMessage(result);
}

2. 回调地狱问题

实例

// 回调地狱示例
fs.readFile('file1.txt', (err, data1) => {
    if (err) throw err;
    fs.readFile('file2.txt', (err, data2) => {
        if (err) throw err;
        fs.readFile('file3.txt', (err, data3) => {
            if (err) throw err;
            // 嵌套越来越深...
            console.log('所有文件读取完成');
        });
    });
});

// 现代解决方案:使用 Promise 和 async/await
const fsPromises = require('fs').promises;

async function readFiles() {
    try {
        const data1 = await fsPromises.readFile('file1.txt');
        const data2 = await fsPromises.readFile('file2.txt');
        const data3 = await fsPromises.readFile('file3.txt');
        console.log('所有文件读取完成');
    } catch (err) {
        console.error('读取文件失败:', err);
    }
}

3. 单线程的脆弱性

实例

// 未捕获的异常会导致整个应用崩溃
setTimeout(() => {
    throw new Error('未捕获的异常');  // 这会导致应用崩溃
}, 1000);

// 解决方案:全局异常处理
process.on('uncaughtException', (err) => {
    console.error('未捕获的异常:', err);
    // 记录日志并优雅关闭应用
    process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的 Promise 拒绝:', reason);
    // 记录日志并优雅关闭应用
    process.exit(1);
});

适用场景分析

1、RESTful API 服务

实例

// 未捕获的异常会导致整个应用崩溃
setTimeout(() => {
    throw new Error('未捕获的异常');  // 这会导致应用崩溃
}, 1000);

// 解决方案:全局异常处理
process.on('uncaughtException', (err) => {
    console.error('未捕获的异常:', err);
    // 记录日志并优雅关闭应用
    process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的 Promise 拒绝:', reason);
    // 记录日志并优雅关闭应用
    process.exit(1);
});

2、实时应用

实例

// WebSocket 实时聊天
const io = require('socket.io')(server);

io.on('connection', (socket) => {
    socket.on('chat message', (msg) => {
        io.emit('chat message', msg);  // 广播消息
    });
});

3、中间件和代理服务

实例

// HTTP 代理服务器
const httpProxy = require('http-proxy-middleware');

const proxy = httpProxy({
    target: 'http://api.example.com',
    changeOrigin: true,
    pathRewrite: {
        '^/api': ''
    }
});