什么是 NodeJS?服务器端 JavaScript 详解
Source: Dev.to
介绍
大家好,开发者朋友们!如果你花时间构建过网页应用,你可能已经听说过 Node.js 的热潮。如果还没有,请查看我之前的博客,我会一步步带你从零开始构建你的第一个基础 Node.js 服务器:Setting Up Your First Node.js Application。
什么是 Node.js,为什么它会爆炸式流行?
在本文中,我们将从头开始探讨 Node.js:
- 它的起源
- 它是如何打破 “JavaScript 只能在浏览器中运行” 的壁垒
- 它的核心架构
- 为什么它会成为后端开发的游戏规则改变者
无论你是想从前端转向全栈的开发者,还是正在探索新工具的后端工程师,阅读完本文后,你都能获得更多信息并获得全新的视角。
注意: 本文基于一些基础概念。如果你想更深入地了解内部实现(例如 Node.js 架构的三大支柱、事件循环的各个阶段,或线程池),请查看我之前的文章:
Node.js 之前的生态
JavaScript 于 1995 年创建,主要用于为网页添加交互性(最初称为 LiveScript)。它在浏览器沙箱中运行,负责 DOM 操作、用户事件、表单验证和网络请求。
与此同时,服务器端则由 Perl、PHP、Java、Ruby 和 Python 等语言主导。这种分工在当时是合乎情理的:
| 浏览器端 | 服务器端 |
|---|---|
| 轻量级、事件驱动的脚本语言 | 强大的 I/O、安全性、系统级访问 |
| 在沙箱中运行 | 在操作系统上运行 |
| 处理 UI 和用户交互 | 处理数据存储、业务逻辑、API |
开发者必须在不同语言之间切换:前端使用 JavaScript,后端使用其他语言。这导致逻辑重复、开发速度变慢以及技能碎片化。
Ryan Dahl的愿景 (2009)
在开发高性能网络应用时,Ryan Dahl 对传统的服务器模型感到沮丧。像 Apache 这样的工具采用 每请求一个线程(thread‑per‑request)方式:每个进入的连接都会产生一个新的线程或进程。这在低流量时还能勉强工作,但在高并发下扩展性差,原因包括:
- 上下文切换开销
- 高内存使用
- 阻塞 I/O(例如等待数据库查询)会使整个线程停滞
Ryan 想要一个能够提供:
- 非阻塞支持
- 针对 I/O 密集型工作负载的高效性(实时应用、聊天、流媒体)
- 端到端使用同一种语言
他尝试了多个运行时后,最终选择了 Google 的 V8 JavaScript 引擎(刚刚开源且速度极快)。2009 年 11 月,他发布了 Node.js —— 一个基于 V8、libuv 和 C++ 绑定构建的运行时,使 JavaScript 能在服务器上以事件驱动、非阻塞 I/O 模型运行。
关键说明:
JavaScript 是编程语言(ECMAScript)。
Node.js 是提供在浏览器之外执行 JavaScript 环境的运行时。
什么是 Node.js?
Node.js 是一个 开源、跨平台的 JavaScript 运行时,基于 Chrome 的 V8 引擎构建。它在浏览器之外执行 JavaScript 代码,使服务器端脚本成为可能。
- V8 负责 JavaScript 的解析、即时编译(JIT)和执行。
- libuv 提供缺失的功能:文件系统访问、网络、定时器以及跨平台的异步 I/O 层。
类比
- JavaScript → 汽车引擎
- 浏览器 → 时尚跑车(针对 UI 进行优化)
- Node.js → 坚固卡车(针对搬运货物:服务器 I/O、数据库、API 进行优化)
V8 – 引擎内部工作原理
Google 的 V8 是用 C++ 编写的高性能 JavaScript 和 WebAssembly 引擎。它使用 即时编译(JIT) 在运行时将 JavaScript 转换为优化后的机器码。
亮点
| 功能 | 描述 |
|---|---|
| 解析与优化 | 隐藏类、内联缓存、先进的垃圾回收机制 |
| 速度 | 在许多任务中接近原生的性能 |
| 限制 | 单独的 V8 对文件、网络或操作系统一无所知——这正是 Node.js 添加其层的地方 |
Node.js 嵌入 V8,并通过 C++ 绑定和 libuv 为跨平台异步 I/O 提供支持。
核心架构:事件驱动、非阻塞 I/O
Node.js 采用基于 libuv 事件循环和线程池的事件驱动、非阻塞 I/O 模型。
- 事件循环 – 持续检查调用栈和任务队列,在操作完成后执行回调。
- 线程池 – 将某些阻塞任务(例如 DNS 查询、繁重的文件 I/O)卸载到线程池,从而保持主事件循环的非阻塞。默认池大小为 4 个线程(可通过
process.env.UV_THREADPOOL_SIZE配置)。
工作原理
- 为慢操作(数据库查询、文件读取、HTTP 请求)注册回调。
- 继续执行 – 主线程继续处理其他事件。
- 当操作完成时,libuv 发出事件,已注册的回调被执行。
大多数 JavaScript 代码在主线程上运行;线程池只处理少数真正会阻塞的操作。这种设计非常适合 I/O 密集型应用,但对 CPU 密集型任务需要谨慎处理(在现代 Node.js 中使用 worker threads)。
浏览器 JS 与 Node.js 执行
| 方面 | 浏览器 JavaScript | Node.js |
|---|---|---|
| 全局对象 | window, document, fetch, WebSocket | global, process, require, fs, http |
| API | DOM、CSSOM、Web API | 文件系统、网络、流、子进程 |
| 事件循环 | 由浏览器引擎管理 | 由 libuv 管理 |
| 模块 | ES 模块(import)(或旧的 script 标签) | CommonJS(require)和 ES 模块 |
| 使用场景 | UI 渲染、客户端交互 | 服务器端逻辑、API、实时服务 |
Node.js 运行时架构概览
+-------------------+ +-------------------+
| JavaScript | | C++ Bindings |
| (V8 Engine) | | (libuv) |
+-------------------+ +-------------------+
^ ^
| |
| Event Loop & Thread |
| Pool (libuv) |
+-------------------------+
|
+-------------------+
| OS / System |
+-------------------+
为什么开发者纷纷转向 Node.js
| 传统模型(例如 PHP) | Node.js 模型 |
|---|---|
| 同步(默认)。每个请求通常会生成一个新进程或线程(Apache、Nginx + PHP‑FPM)。 | 异步(默认)。单线程通过事件循环处理大量并发连接。 |
| 阻塞 I/O 会使整个进程停滞。 | 非阻塞 I/O 使线程保持空闲以处理其他工作。 |
| 多语言 需要用于前端(JS)和后端(PHP、Ruby 等)。 | 单语言(JavaScript)端到端,减少上下文切换和重复逻辑。 |
| 每个请求更高的内存占用。 | 更低的内存使用——事件循环使用小堆即可处理大量连接。 |
结束语
Node.js 不仅仅是“服务器端的 JavaScript”。它是一个精心构建的环境,将浏览器式的事件处理带到后端,使得使用单一语言栈即可构建高度可扩展、I/O 密集型的应用程序。
如果你刚刚入门,可以尝试一个简单的 HTTP 服务器:
// hello.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, Node.js!\n');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
使用以下命令运行它:
node hello.js
从这里你可以探索丰富的 npm 包生态系统,深入了解事件循环内部实现,或使用 WebSocket 构建实时应用。祝编码愉快!
PHP(阻塞模型)
- 适用于简单的动态页面。
- 对并发连接的效率较低 – 阻塞调用会占用资源。
Java(线程模型)
- 对于具有强大线程支持和成熟库的企业工作负载表现出色。
- 管理线程、回调或响应式框架会增加复杂性和开销。
Node.js (事件‑驱动模型)
| 功能 | 好处 |
|---|---|
| 单线程事件循环 | 思维模型更简单,竞争条件更少。 |
| 非阻塞 I/O | 以低内存使用处理成千上万的并发连接。 |
| 前后端统一语言(JS/TS) | 开发更快,代码/类型可共享。 |
| 通过 npm 的庞大生态系统 | 最大的包注册表,数不清的可复用模块。 |
采纳驱动因素
- 实时应用随着 WebSockets(聊天、实时更新、流媒体)成为主流。
- 全栈 JavaScript 减少了开发者的上下文切换。
- 微服务和 API 更青睐轻量、可扩展的运行时。
- PayPal、Uber 和 Netflix 等公司在采用 Node.js 后报告了巨大的性能和生产力提升。
注意: Node.js 并非在所有场景都是最佳工具(如视频编码等 CPU‑密集型任务可能更适合 Go 或 Java),但在合适的场景下表现出色。
常见使用场景
- RESTful API: Express、Fastify、NestJS。
- 实时应用: WebSockets(Socket.io、原生
ws)、协作工具、游戏后端。 - 流媒体与微服务: Netflix 将其用于边缘逻辑和数据处理。
- CLI 工具: npm、webpack 以及无数开发工具都是用 Node.js 构建的。
- 物联网与边缘服务器: 轻量足迹在受限设备上运行良好。
- 无服务器: AWS Lambda、Vercel 等都喜欢 Node.js 的快速冷启动。
为什么 Node.js 很重要
Node.js 不仅把 JavaScript 带到了服务器——它 让后端开发大众化,并证明了简单的事件驱动模型能够驱动全球最大规模的应用。通过解决 Ryan Dahl 最初的痛点,结合 V8 的速度和 libuv 的优雅,它创建了一个运行时,具备:
- 高生产力 – 快速原型和迭代。
- 可扩展性 – 用最少资源处理海量并发。
- 有趣 – 直观的异步模式和充满活力的社区。
曾被视为浏览器“玩具”语言的 JavaScript,如今因 Node.js 成为 全栈强力引擎。统一的生态系统、活跃的社区以及持续的改进(工作线程、ESM 支持、更佳性能)使其在 2026 年及以后 仍然保持相关性。
入门
-
从 nodejs.org 安装 Node.js。
-
验证安装:
node -v npm -v -
使用内置的
http模块创建一个简单的 HTTP 服务器:// server.js const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, Node.js!\n'); }); server.listen(3000, () => { console.log('Server running at http://localhost:3000/'); }); -
运行它:
node server.js -
探索 Express 或 NestJS 等框架,尝试 async/await,并深入了解事件循环——真正的 “啊哈” 时刻就在这里。
你对 Node.js 有什么经验?
你是从 PHP/Java 迁移过来,还是在构建你的第一个全栈 JS 应用?在评论中留下你的想法。如果你喜欢这篇概览,请分享或查看上面链接的更深入的架构文章。
编码愉快! 🚀
参考文献
- 官方 Node.js 文档
- Ryan Dahl 的演讲
- V8 博客
- libuv 文档
- 社区资源(npm、GitHub、博客)