从零上手 Node.js:模块、HTTP 服务器、文件系统与事件机制实战指南
【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
导读
本篇指南以 Node.js 的入门学习路径为主线,系统讲解在 Node.js 中运行脚本、创建与使用模块、用 HTTP 模块搭建基础 Web 服务器、用 fs 模块读写文件、用 URL 类解析地址以及使用 EventEmitter 处理事件等核心能力。读完本篇后,你将能够脱离浏览器、纯用 JavaScript 编写可在本机或服务器上运行的后端程序,并为课程末尾的 Basic Informational Site(基础信息站点)项目打下完整基础。本文基于仓库中的 getting_started.md 一课展开,并辅以同一课程路径下的 introduction_what_is_nodeJS.md、environment_variables.md、debugging_node.md 与 project_basic_informational_site.md 进行深化。
前置准备:为什么先学 JavaScript 再学 Node
在动手之前需要明确一个基本事实:Node.js 本质上就是 JavaScript。正如 getting_started.md 开头强调的,只有具备基本的 JavaScript 基础,才能理解 Node 的语法与行为。Node 并不是一门新语言,而是一个把 JavaScript 从浏览器中"解放"出来的运行时。
这一点在 introduction_what_is_nodeJS.md 中有更详细的阐述:JavaScript 最初被设计为只能在浏览器内运行,这导致它无法编写网站以外的任何程序。Node 的出现让开发者可以用 JavaScript 完成 Ruby、PHP、C#、Python 等传统服务端语言能够完成的几乎所有事情——在本地计算机或服务器上直接运行 JavaScript,而无需经过浏览器。
为了支撑服务端场景,Node 在浏览器 JavaScript 之外额外提供了一些能力,其中包括:
- 读取和写入本地文件;
- 创建 HTTP 连接;
- 监听网络请求。
本章的学习目标非常明确(来自原文的 Lesson overview):
- 说明 Node.js 的常见用途;
- 创建和使用模块(既包括内置模块,也包括用户自定义模块);
- 使用 HTTP 模块搭建基础 Web 服务器;
- 从 Node.js 中读取、创建、更新和删除文件;
- 使用 URL 模块解析网址并将其拆分为可读部分;
- 理解如何使用 NPM;
- 创建、触发并监听自己的事件。
理解 Node 的异步与事件驱动模型
在编写第一段服务端代码前,有必要先理解 Node 的运行哲学。Node 官网将其定义为"异步事件驱动的 JavaScript 运行时",这个定义可以拆解为两层含义:
- 异步(asynchronous):写代码时不需要预测每一行代码的精确执行顺序。程序被组织为一系列小函数,这些函数在特定事件(如网络请求)发生时被调用;
- 事件驱动(event driven):程序在等待某个过程完成时,并不阻塞后续代码,而是"坐等"事件发生,事件一到就触发对应的回调函数。
introduction_what_is_nodeJS.md 给出了一个非常直观的例子:假设程序需要"从文件读取文本 → 打印文本内容"以及"查询数据库用户列表 → 按年龄过滤用户"。如果同步执行,必须逐条等待;而异步执行时,Node 会在等待文件读取的过程中立即启动数据库查询,两个任务并行推进,谁先完成谁先触发后续回调,程序员无需关心二者的完成顺序。
这与前端 JavaScript 中使用addEventListener等待鼠标点击或键盘按键的思路几乎一致,区别只在于 Node 监听的事件是网络请求和数据库查询这类服务端事件,而这一机制正是通过**回调函数(callbacks)**实现的。
下面这段代码就是整个课程中最早的 Node 服务端示例:
http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); res.end('Hello World!'); }).listen(8080);这段代码创建了一个服务器,并声明:"每当收到一个网络请求,就执行这个回调函数"。回调函数向客户端返回文本Hello World!。在浏览器中访问正确的地址和端口,就能在屏幕上看到这行文本。它也是下一篇 HTTP 模块的主角。
在终端中运行 Node.js 脚本
入门的第一步是学会从命令行运行 Node 脚本。与浏览器中通过<script>标签加载 JavaScript 不同,Node 脚本是独立的文件,通过node命令直接执行:
node your-script.js例如,把上面的 HTTP 示例保存为server.js,然后在项目目录下执行node server.js,Node 就会启动这个脚本并保持监听。需要中断运行时,在终端按下Ctrl + C即可。
从仓库中可以看到,后续课程大量使用这种"命令行驱动"的工作流:例如 introduction_to_express.md 中通过node app.js启动 Express 服务器,environment_variables.md 中则展示了NODE_ENV=prod node index.js这类带环境变量的运行方式。这说明"运行 Node 脚本"是贯穿整个 Node.js 课程的基础操作。
内置模块与用户自定义模块
Node 的功能是以模块为单位组织的。模块化意味着你可以把可复用的代码封装成独立文件,在需要的地方引入。Node 的模块体系分为两类:
- 内置模块:Node 自带,如
http、fs、url、events、path等,直接require即可使用,无需安装; - 用户自定义模块:你自己编写并导出的代码文件,通过
module.exports导出、通过require('./path')引入。
一个典型的用户自定义模块如下:
// math.js function add(a, b) { return a + b; } module.exports = { add };// index.js const { add } = require('./math'); console.log(add(2, 3)); // 5这种"按需引入、职责分离"的组织方式,正是后面课程中拆分路由、控制器等代码结构的基础。接下来几节将逐一介绍入门必备的四个内置模块。
HTTP 模块:创建基础 Web 服务器
HTTP 模块是 Node 服务端编程的核心。它提供了创建 HTTP 服务器和发起 HTTP 请求的能力。
使用 http.createServer 创建服务器
http.createServer是 HTTP 模块中最常用的方法:它创建一个 HTTP 服务器,并接收一个处理函数(handler),该处理函数会在每次收到请求时被执行。如原文所述:
createServer方法创建一个 HTTP 服务器,该服务器接收每次收到请求时都会执行的处理函数。
服务器对象本身具备listen方法,用于指定监听端口。综合前文,一个最小可运行的服务器如下:
const http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); res.end('Hello World!'); }).listen(8080);这里有几个要点:
req是请求对象,包含客户端发来的方法、URL、请求头等信息;res是响应对象,res.writeHead用于写入状态码(如200表示成功)和响应头(如Content-Type: text/html),res.end结束响应并发送正文;.listen(8080)让服务器监听本机 8080 端口,访问http://localhost:8080即可看到响应内容。
发起 HTTP 请求
服务端场景不仅需要接收请求,也常常需要主动向外发起请求(例如调用第三方 API)。Node 提供了fetch(基于浏览器同款 Fetch API)等能力用于发起 HTTP 请求。在入门阶段,先掌握"接收请求并返回响应"即可,主动发起请求会在后续 working_with_apis.md 等课程中进一步深化。
从回调到路由分发
回调函数中的req.url可以用来区分不同路径,从而分发到不同页面。这正是本课程末尾项目(Basic Informational Site)的核心逻辑:
- 访问
/返回index.html; - 访问
/about返回about.html; - 访问
/contact-me返回contact-me.html; - 其他所有路径返回
404.html。
不过仅靠http模块手写路由会比较啰嗦,这也是后续引入 Express 框架的原因(详见 introduction_to_express.md 中对"请求的旅程"和中间件机制的讲解)。入门阶段先用原生http模块亲手实现一遍,才能深刻理解框架替你做了什么。
文件系统模块:读写文件
服务端程序离不开文件操作,Node 的fs(File System)模块正是为此而生。它是 Node 中"重度使用"的模块之一,覆盖文件的读取、创建、更新和删除。
写入文件
写入文件最常用的方法是fs.writeFile。注意fs模块的大多数方法都同时提供异步(回调风格)与同步(带Sync后缀)两种版本,异步版本不会阻塞事件循环:
const fs = require('fs'); // 异步写入:文件不存在则创建,存在则覆盖 fs.writeFile('message.txt', 'Hello Node!', (err) => { if (err) throw err; console.log('文件已写入'); });对应地,fs.appendFile可以在已有文件末尾追加内容,适合日志类场景;fs.rename用于重命名文件;fs.unlink用于删除文件。它们共同构成了原文所说的"读取、创建、更新和删除"四类操作:
// 追加内容(创建/更新) fs.appendFile('message.txt', '\nSecond line', (err) => { if (err) throw err; }); // 重命名(更新) fs.rename('message.txt', 'renamed.txt', (err) => { if (err) throw err; }); // 删除 fs.unlink('renamed.txt', (err) => { if (err) throw err; });读取文件
读取文件使用fs.readFile,回调的第二个参数即为文件内容:
const fs = require('fs'); fs.readFile('message.txt', 'utf8', (err, data) => { if (err) { console.error(err); return; } console.log(data); });传入'utf8'编码参数后,data就是可直接使用的字符串;省略该参数时,data会是 Buffer(二进制缓冲区)。在 Basic Informational Site 项目中,fs.readFile正是"按 URL 读取对应 HTML 文件并返回给客户端"的关键手段。
URL 类:解析网址为可读部分
处理网络请求时,经常需要把网址拆解为协议、主机名、路径、查询参数等可读部分。Node 的url模块提供 WHATWG 标准的URL类来完成这项工作:
const myURL = new URL('https://example.org:8080/path/to/page?name=ferret&age=3'); console.log(myURL.hostname); // 'example.org' console.log(myURL.port); // '8080' console.log(myURL.pathname); // '/path/to/page' console.log(myURL.search); // '?name=ferret&age=3' console.log(myURL.searchParams.get('name')); // 'ferret'URL实例暴露了hostname、port、pathname、search、searchParams等大量只读属性,searchParams还提供了get、set、append、delete等方法,方便读写查询参数。原文建议"亲手运行这些代码示例看看它是如何工作的",因为这正是把"请求的 URL"映射到"要返回的页面文件"这一路由逻辑的数据基础。
Events 模块:创建、触发与监听事件
Node 是事件驱动的,而事件机制的底层实现就是events模块中的EventEmitter(事件发射器)类。你可以创建自己的事件发射器,用on(或addListener)注册监听器,用emit触发事件:
const EventEmitter = require('events'); const myEmitter = new EventEmitter(); // 监听 'greet' 事件 myEmitter.on('greet', (name) => { console.log(`Hello, ${name}!`); }); // 触发 'greet' 事件,并携带参数 myEmitter.emit('greet', 'Node');事件发射器可以同时注册多个监听器,事件触发时它们会按注册顺序依次执行;emit传入的额外参数会被透传给监听器函数。理解EventEmitter之后,你会发现自己写的 HTTP 服务器本质上也是一个"在收到请求时触发回调"的事件机制——http.createServer的回调、fs.readFile的回调,都是事件驱动模型的具体体现。
NPM 与 package.json
NPM(Node Package Manager)随 Node 一同安装,是 Node 生态的包管理工具,用于安装、管理和发布第三方依赖。入门阶段需要掌握三个基本命令:
# 初始化项目,生成 package.json npm init -y # 安装依赖(-D 表示作为开发依赖) npm install <package-name> npm install -D <package-name> # 运行 package.json scripts 中定义的脚本 npm run <script-name>package.json是项目的"身份档案",记录项目名称、版本、脚本和依赖。本仓库根目录的 package.json 就是一个典型实例,其中定义了lint、fix、test三个 npm script,并通过devDependencies引入markdownlint-cli2作为开发依赖:
{ "name": "curriculum", "version": "1.0.0", "scripts": { "lint": "markdownlint-cli2", "fix": "markdownlint-cli2 --fix", "test": "node --test" }, "license": "CC BY-NC-SA 4.0", "devDependencies": { "markdownlint-cli2": "^0.23.2" } }这个文件展示了三个可迁移到任何 Node 项目的实践:用scripts固化常用命令、用devDependencies区分仅开发期需要的工具、用语义化版本范围(^0.23.2)声明依赖版本。在 introduction_to_express.md 中,Express 的安装也正是通过npm init -y加npm install express完成的,可见 NPM 是后续所有第三方框架学习的前提。
实战演练:Basic Informational Site 项目
学习完上述模块后,就到了动手环节。本课程末尾的项目 project_basic_informational_site.md 要求用 Node 创建一个包含 4 个页面的基础信息站点。项目规格如下:
- 在项目目录中创建 4 个 HTML 文件:
index.htmlabout.htmlcontact-me.html404.html
- 创建 Node 服务器文件
index.js,编写代码根据 URL 返回对应页面:http://localhost:8080→ 返回index.htmlhttp://localhost:8080/about→ 返回about.htmlhttp://localhost:8080/contact-me→ 返回contact-me.html- 访问上述以外的任何路径 → 返回
404.html
结合本篇所学,一个可行的实现骨架是:用http.createServer接收请求,读取req.url判断路径,再用fs.readFile读取对应的 HTML 文件并通过res返回;路径不匹配时回退到404.html,同时通过res.writeHead设置Content-Type: text/html和合适的 HTTP 状态码。项目刻意强调"页面内容并不重要,不需要花时间美化",目的是把全部注意力放在 Node 的服务端逻辑上。
完成这个项目后,如果你觉得手写路由和文件分发比较繁琐,可以对照 introduction_to_express.md 的作业要求,安装 Express 用几个app.get()重写一遍同样的项目——这将是理解"框架为什么存在"的最佳对比实验。
进阶补充:环境变量与调试
入门之后,还有两个与 Node 日常开发强相关的能力值得一并掌握。
用环境变量隔离配置
environment_variables.md 介绍了如何让不同环境(本机开发、云端部署)使用不同的配置。环境变量可以通过以下方式加载:
- 直接在启动命令中赋值:
NODE_ENV=prod node index.js; - 使用 shell 的
export命令写入当前会话; - 使用
.env文件(Node 内置支持,通过node --env-file=.env index.js或process.loadEnvFile()加载); - 使用 dotenv 等第三方库(老项目中常见)。
在代码中通过process.env读取,例如:
const PORT = process.env.PORT || 3000;注意:环境变量的值永远是字符串,需要数值或布尔时须自行转换;包含敏感信息的.env文件必须加入.gitignore,绝不可提交到仓库。
用调试器定位问题
debugging_node.md 则强调:服务端代码不能依赖浏览器 DevTools,而应学会使用 VS Code 内置的 Node 调试器,在编辑器中直接打断点、观察变量。VS Code 的 JavaScript Debug Terminal 是最便捷的启动方式之一,这是从"会写 Node"走向"能调好 Node"的关键工具。
知识自检
读完本篇后,试着回答以下问题(对应原文档的 Knowledge check):
- 什么是文件系统模块?你会如何使用它、为什么使用它?
- 如何在终端中运行一个 Node.js 脚本?
http.createServer的回调函数在什么时机被执行?- 如何用
fs模块读取和写入文件? - 用
URL类如何拿到网址的路径和查询参数? - 如何创建自己的
EventEmitter并监听、触发事件? - 为什么说 Node 是"异步事件驱动"的运行时?
如果这些问题都能清晰作答,并且能独立完成 Basic Informational Site 项目,你就已经跨过了 Node.js 入门的门槛,可以带着对回调、模块、事件和文件操作的理解,进入 Express 与更复杂的后端开发学习了。
【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考