NodeJs

Node.js 是什么

转载:小红书 程序员枝楠

前言🔖


Node.js 并不是一门新的编程语言,它本质是一款能够让 JavaScript 在服务器端运行的运行时环境。 简单来讲,它赋予了 JavaScript 脱离浏览器运行的能力,使其可以像 PHP、Python 一样,直接在电脑中执行代码。本文会逐层拆解,带你彻底弄懂 Node.js 的核心定义、它能成为后端热门技术的原因,以及区别于传统服务端语言的独特设计逻辑与运行流程。

  

一、诞生初衷:解决传统服务器高并发资源浪费问题🔖


Node.js 的诞生,核心是为了解决传统 Web 服务器处理高并发请求时严重的资源损耗缺陷。

以 Apache 这类传统服务器为例,它采用「一请求一线程」模型:每一个用户请求,都会单独分配一条独立线程进行处理。

如果网站同时涌入数千用户访问,服务器就要同步创建数千条线程,每条线程都会持续占用内存资源。一旦访问量暴涨,内存资源会被快速耗尽,服务器极易出现卡顿、崩溃。

Node.js 颠覆了这套老旧模型:它基于谷歌 V8 引擎(Chrome 浏览器同款 JavaScript 解析引擎),独创单线程非阻塞 IO架构。

我们可以用银行窗口的例子直观理解这套机制: 银行仅开设一个业务窗口,但柜台人员处理业务效率极高,可同时承接多名客户的叫号排队。当某位客户需要后台调取资料(对应读取文件、查询数据库等耗时 IO 操作)时,工作人员不会原地等待,而是立刻接待下一位客户。 这就是非阻塞 IO 的核心逻辑:程序遇到需要等待的 IO 操作时,不会停下进程阻塞等待结果,而是转去处理下一条新请求;等 IO 操作完成、数据返回后,再通过事件循环机制,回头执行对应业务逻辑。

  

🔹延伸小知识

Node.js是非阻塞IO,但是查询数据库,读写文件,难道不会阻塞IO吗?

Node.js 主线程不会被数据库、文件读写阻塞,查询数据库 / 读写文件时,Node 完全可以正常响应其他用户请求。

关键区分两个概念:主线程 JS 执行阻塞 vs 底层操作系统 IO 等待(Node 单线程 + 非阻塞 IO 的底层分工)。

Node.js 分为两层:

  • JS 主线程(事件循环 Event Loop) 只负责跑 JavaScript 逻辑、回调函数、事件分发,所有业务代码都在这里串行执行;
  • libuv 线程池 / 操作系统内核异步 IO 文件读写、数据库网络请求、网络 IO 这类耗时等待操作,全部交给底层,不占用主线程

libuv 是一个跨平台 C 语言编写的异步 IO 底层库,是 Node.js 实现「单线程、非阻塞 IO、事件循环」的核心底层基石。 没有 libuv,V8 引擎只能跑 JS,根本做不了服务端、文件、网络、定时器等操作。

V8 只负责执行 JavaScript 代码,没有操作系统交互能力; 你写的 fsnethttp、数据库网络请求、定时器、子进程等内置 API,底层全部中转到 libuv; libuv 是 Node 和操作系统之间的中间层,统一封装 Windows/macOS/Linux 不同系统的底层 IO 接口:

  • Linux:epoll
  • macOS:kqueue
  • Windows:IOCP

libuv 调用系统内核 API 完成网络、文件、进程、异步等待等操作; IO 操作完成后,libuv 通过事件循环把回调抛回 JS 主线程执行。

极简链路:JS 代码 → Node 内置模块 → libuv → 操作系统内核

  

Node.js 整体分层(从上到下)

  1. JavaScript 业务代码(你写的接口、查询数据库、fs 读写)
  2. Node.js 内置 API(fs /http/mysql 网络请求 /setTimeout)
  3. libuv(核心调度层)
  4. 操作系统底层 API(Windows IOCP、Linux epoll、macOS kqueue)

V8 只负责执行 JS 代码;所有和操作系统打交道的脏活、异步等待、线程管理,全是 libuv 干的。

  

libuv 还管理哪些功能?

  1. 定时器 setTimeout / setInterval
  2. 进程、子进程管理
  3. 信号监听、跨平台兼容(一套代码跑 Windows、Mac、Linux)
  4. 异步 TCP/UDP 网络
  5. 管道、文件监控

  

二、核心优势:IO 密集型场景高性能、低内存占用🔖


这套单线程非阻塞 IO 的设计,让 Node.js 极其适配 IO 密集型业务场景,例如实时聊天系统、在线协同工具、流媒体后端、高并发负载均衡服务等。

相比多进程、多线程架构的传统服务器,它的内存消耗极低:同样承载 1000 条并发请求,传统 PHP 服务可能需要占用 1GB 内存,而 Node.js 仅需几十 MB 内存即可稳定运行。

之所以能做到轻量化,是因为它无需为每一条请求新建独立线程,全部请求都在同一条主线程内调度流转。

  

🔹阻塞IO的例子

CPU 密集同步 JS 运算(卡死服务典型例子)

主线程一直在做循环计算,事件循环停住,所有新请求排队,浏览器发请求会一直转圈超时。

const http = require('http');

const server = http.createServer((req, res) => {
  // 只要有人访问,立刻执行超大循环,阻塞主线程
  let total = 0;
  // 十亿次循环,CPU打满,卡住整个服务
  for (let i = 0; i < 1_000_000_000; i++) {
    total += i;
  }

  res.end(`计算完成:${total}`);
});

server.listen(3000, () => {
  console.log('服务启动 3000 端口');
});

现象:
第一个用户进来,开始疯狂计算;
这期间任何新用户访问 3000 端口都无响应、超时;
数据库查询、文件读写、定时器全部停滞。

同步 IO 方法(同步读文件、同步数据库等)

Node 所有带 Sync 后缀的 API 都是阻塞主线程 IO,等待 IO 完成前,不处理任何其他请求。

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

const server = http.createServer((req, res) => {
  // 同步读文件:主线程卡死,等待磁盘读取完成
  const content = fs.readFileSync('./big-log.txt', 'utf8');
  res.end(content.slice(0, 100));
});

server.listen(3000);

只要磁盘读取没结束,所有外部请求全部卡住。

  

三、天生短板:不擅长 CPU 密集型任务,配套解决方案🔖


单线程架构同时也带来了明显短板:如果代码中存在重度 CPU 计算逻辑,会直接阻塞整条事件循环。

举几个典型场景:处理高分辨率图片、压缩大型视频、复杂加密解密运算。这类任务会独占唯一主线程,相当于银行窗口被一项耗时业务锁死,后续所有用户请求全部排队等待,服务器整体性能断崖式下跌。

因此 Node.js 天生不适合 CPU 密集型业务,在这类场景下,Java、Go 等原生多线程语言更具备优势。

为弥补这一缺陷,Node.js 提供了工作线程模块。开发者可以将重度 CPU 计算的子任务拆分,交给独立的子线程执行,计算完成后再将结果传回主线程。但这种方式本质上绕开了原生单线程设计,需要开发人员手动拆分任务,增加了开发成本。

  

四、生态王牌:npm 全球最大软件包库🔖


Node.js 另一大核心竞争力,是配套的包管理器 npm。

npm 是全球规模最大的软件包注册仓库,几乎所有开发需求,都能找到成熟开源工具包直接复用。

举例:搭建网站用户登录体系,无需从零编写控制器、路由、会话管理逻辑,仅一行命令就能安装 Express 服务框架与 Passport 认证工具。

完善的开源生态大幅压缩了开发周期,个人开发者、小型团队也能快速搭建功能完整的线上应用。

  

五、主流落地应用场景🔖


在具体应用场景上,Node.js最常见的用途是作为前端构建工具的服务端环境,比如 webpack、Vite这些打包工具底层都跑在Node.js上。此外它还非常做API网关或微服务。

  1. 前端工程化构建工具 webpack、Vite 等前端打包、编译工具底层均基于 Node.js 运行,是前端开发必不可少的环境;
  2. 轻量 API 服务、微服务、网关 Node.js 处理网络请求效率突出,单台服务器可轻松承载上万条简单 HTTP 请求。PayPal 等企业曾将原有 Java 业务重构为 Node.js,落地后实现了性能、启动速度、开发效率全方位提升。

  

六、总结🔖


Node.js 并非能够包揽所有后端业务的万能工具,它更像一块针对性极强的技术拼图:

在海量简单并发 IO 的业务场景中表现优异,但面对重度 CPU 计算任务时需要额外做适配处理。

如果你想要搭建高实时、高并发的网络应用,或是前端开发者希望拓展技术栈实现前后端统一使用 JavaScript 开发,Node.js 是非常值得学习的技术。

它没有改动 JavaScript 语言本身,却为 JavaScript 开辟了浏览器之外、服务端开发的全新应用领域。