Menu

Node.js 运行时:JavaScript 如何在浏览器之外运行

Node.js 运行时是什么,它和浏览器有什么不同,以及让服务器端 JavaScript 成为可能的核心 API:全局对象、模块、process 和 fs。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

Node 到底是什么

Node.js 是一个装在你电脑上、用来运行 JavaScript 文件的程序。这是最简单的描述,而且说得没错。当你输入 node script.js 时,Node 读取这个文件,把它交给 Google 的 V8 引擎(Chrome 使用的 JavaScript 引擎)执行,同时附带一个庞大的额外 API 库,用来完成 V8 自己做不到的事情。

V8 知道怎么运行 JavaScript。但它不知道怎么打开文件、监听 TCP 套接字、启动进程或读取环境变量。这些都由 Node 用 C++ 实现,再以内置模块的形式提供给你的 JavaScript 代码。

node --version
node script.js

所以 Node 不是一门语言,也不是一个框架。它是一个运行时:V8 加上标准库,加上模块系统,再加上事件循环。全部就是这些。

第一个脚本

任何 .js 文件都是合法的 Node 程序。不需要样板代码,也不需要 main 函数:

console.log 的用法和在浏览器里一样。模板字符串、Date、数组、Promise,你已经熟悉的所有语言特性都来自 V8,行为完全相同。在 Node 中改变的,是语言之外可用的东西。

只在 Node 中存在的全局对象

浏览器有 window、document、localStorage、fetch。Node 有另一套全局对象,是围绕服务器端运行时设计的:

  • process 代表正在运行的 Node 进程。它包含环境变量(process.env)、命令行参数(process.argv),以及退出进程的方法(process.exit(1))。
  • __filename 和 __dirname 给出当前文件及其所在文件夹的绝对路径。(在 ES 模块中它们不存在,要改用 import.meta.url。)
  • global 是顶层对象,相当于 Node 里的 window。

这里没有 document,也没有 window。如果你尝试使用它们,会得到 ReferenceError。这通常是第一个信号,说明某个库是为浏览器编写的,无法直接在 Node 中运行。

命令行参数和环境变量

Node 的很大一部分用途,比如命令行工具、构建脚本和服务器,都需要读取参数和环境变量。两者都在 process 上:

process.argv 是一个数组:前两项是 Node 可执行文件的路径和脚本路径,所以真正的参数从索引 2 开始。process.env 是一个存放环境变量的普通对象,从中读取 NODE_ENV、PORT 或 API 密钥是标准做法。

内置模块

Node 自带一个标准库,通过 require(CommonJS)或 import(ESM)使用。模块名以 node: 开头,明确表示它们是内置的:

最常用的几个:

  • node:fs:读写文件。需要 async/await 版本时用 node:fs/promises。
  • node:path:以跨平台的方式拼接、解析和拆分文件路径。
  • node:http / node:https:搭建 HTTP 服务器和发送请求。
  • node:url:解析和构造 URL。
  • node:os:获取主机的信息。
  • node:crypto:哈希、随机字节和加密。

这些模块不需要安装,它们随 Node 一起提供。其他的一切都来自 npm。

事件循环简介

Node 在单线程上运行你的 JavaScript,却能同时处理很多事情。诀窍就是事件循环。当你调用异步操作时,比如读取文件、发送 HTTP 请求或设置定时器,Node 会把实际的工作交给操作系统(或它自己的线程池),然后继续执行。工作完成后,回调会进入队列,等当前代码执行完毕,事件循环再把它取出来。

打印顺序是 1、4、2、3。同步代码最先运行,然后是微任务(已解决的 Promise),最后是定时器。这就是为什么一个耗费 CPU 的慢循环会阻塞整个服务器:执行你的代码的只有一个线程。Node 的并发针对的是 I/O,而不是计算。

一个迷你 HTTP 服务器

学了这么多,回报就是:一个能用的 Web 服务器只需要几行代码:

没有框架,也没有依赖。createServer 接收一个函数,每个请求都会运行它;listen 让事件循环开始处理传入的连接。真实的应用会在这之上使用 Express 或 Fastify,但底层还是同一个内置的 http 模块。

Node 与浏览器

有必要明确列出哪些能通用、哪些不能:

两者都可用仅 Node仅浏览器
语言特性(类、Promise、async/await)fs、http、process、__dirnamewindow、document、DOM
console.logCommonJS require / Node 特有的 ESM 细节localStorage、sessionStorage
fetch(Node 18+)访问文件系统和网络套接字用户事件、渲染
setTimeout、setInterval子进程、流History API、navigator

现代 Node 已经引入了好几个浏览器 API,比如 fetch、URL、AbortController、structuredClone,所以两者的差距比过去小了。但 DOM 不会来到 Node,文件系统也不会来到浏览器。

Node、Deno 与 Bun

Node 是默认选择,但已经不是唯一的 JavaScript 运行时。Deno 和 Bun 是它的替代品:Deno 出自 Node 的原作者,Bun 来自一个专注于速度的新团队。它们运行 JavaScript(也原生支持 TypeScript),内置测试运行器和打包器等工具,在模块处理、权限和包安装方面与 Node 有所不同。

对于学习 JavaScript 来说,文档、教程和招聘岗位仍然集中在 Node 上。事件循环、模块、内置 API 这些概念几乎可以完全迁移到其他运行时。先学 Node,等项目需要时再去了解其他运行时。

快速运行脚本

开发过程中实际执行代码的几种方式:

# Run a file
node script.js

# Run a one-liner
node -e "console.log(2 ** 10)"

# Open the REPL (interactive prompt)
node

# Watch a file and re-run on save (Node 18.11+)
node --watch script.js

REPL 是一个很好用的草稿本,想看看某个方法返回什么时,不用新建文件。--watch 在开发时很方便:一保存,Node 就会自动重新运行你的脚本。

下一章:捕获错误

运行代码是一回事,出问题时如何处理是另一回事。读取文件会失败,HTTP 请求会超时,JSON 解析会抛出异常。下一章讲 try/catch、错误类型,以及处理各种故障的常用模式。在 Node 程序里,迟早每样东西都会出故障。

常见问题

Node.js 运行时是什么?

Node.js 是一个在浏览器之外运行 JavaScript 的程序。它把 Google 的 V8 引擎(Chrome 里运行 JS 的就是它)和一层 C++ 代码打包在一起,这层代码提供了 V8 本身没有的 API:文件系统访问、网络、进程和定时器。正是这个组合,让你能用 JavaScript 编写服务器、命令行工具和构建工具。

Node 和浏览器有什么区别?

两者都运行 JavaScript,但语言之外的 API 不同。浏览器给你 window、document 和 DOM。Node 给你 process、fs、http、__dirname,以及 CommonJS/ESM 模块加载。Node 里没有 DOM,浏览器里没有文件系统:语言是共通的,平台不是。

Node.js 是框架还是运行时?

是运行时。Node 本身不规定任何应用结构,它只负责执行 JavaScript 并提供 API。Express、Next.js、NestJS 这样的框架都构建在 Node 之上。Deno 和 Bun 是另外两个 JavaScript 运行时,它们和 Node 竞争,但扮演的是同样的角色。

Node 中的事件循环是什么?

事件循环让 Node 能在单线程上同时处理很多事情。当你调用异步操作时,比如读取文件、发送 HTTP 请求或 setTimeout,Node 会把工作交给系统,然后继续往下执行。工作完成后,它的回调会进入队列,由事件循环取出执行。所以 fs.readFile 不会阻塞程序的其余部分。

Coddy 编程语言插图

和 Coddy 一起学编程

开始