资源
Node.js 教程
教程
-
脚本模式:
node helloworld.js。 -
交互模式:跟 Python 差不多的交互方式。
node
Welcome to Node.js v22.11.0.
Type ".help" for more information.
> console.log("Hello, world!")
Hello, world!
undefined
>创建第一个应用
创建一个 server.js:
var http = require('http'); // 引入 http 模块
http.createServer(function(req, res) { // 创建 HTTP 服务器
// 调用 res.writeHead 来设置响应的状态码和头部信息。
// 在这里,状态码 200 表示请求成功,
// Content-Type 被设置为 text/plain,意味着响应的内容是纯文本格式
res.writeHead(200, {'Content-Type': 'text/plain'});
// 通过 res.end 方法结束响应,并向客户端发送内容 'Hello World\n'。
// 这将是客户端接收到的响应体。
res.end('Hello World\n');
// listen 方法使得服务器开始监听在本地 IP 地址 127.0.0.1 上的 8888 端口。这意味着服务器会接受发往该地址和端口的请求。
}).listen(8888, '127.0.0.1');
// 输出服务器状态(只有服务端可见)
console.log('Server running at http://127.0.0.1:8888/');启动它!
node server.js
Server running at http://127.0.0.1:8888/客户端将收到 HTTP 响应,由 4 个部分组成:状态行、消息报头、空行和响应正文。响应正文内容如下:
<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">Hello World
</pre></body></html>NPM 使用介绍
NPM(Node Package Manager)是一个 JavaScript 包管理工具,也是 Node.js 的默认包管理器。
使用 npm 命令安装常用的 Node.js web 框架模块 express:
npm install express
added 66 packages in 3s
13 packages are looking for funding
run `npm fund` for details全局安装(后面加后缀 -g):
npm install express -g
added 66 packages in 888msNPM 提供了很多命令,可以使用 npm help 可查看所有命令。
| 命令 | 说明 |
|---|---|
npm init | 初始化一个新的 package.json 文件,交互式输入信息。 |
npm init -y | 快速创建带有默认设置的 package.json 文件。 |
npm install package-name | 本地安装指定包。 |
npm install -g package-name | 全局安装指定包,使其在系统范围内可用。 |
npm install | 安装 package.json 中列出的所有依赖。 |
npm install package-name --save-dev | 安装包并添加到 devDependencies。 |
npm update package-name | 更新指定的依赖包。 |
npm uninstall package-name | 卸载指定的依赖包。 |
npm uninstall -g package-name | 全局卸载指定的包。 |
npm list | 查看当前项目的已安装依赖包列表。 |
npm list -g --depth=0 | 查看全局已安装的依赖包列表(不展开依赖树)。 |
npm info package-name | 查看包的详细信息,包括版本和依赖等。 |
npm login | 登录到 NPM 账号。 |
npm publish | 发布当前包到 NPM 注册表。 |
npm unpublish package-name | 从 NPM 注册表中撤销发布的包(一般限 24 小时内)。 |
npm cache clean --force | 清理 NPM 缓存。 |
npm audit | 检查项目依赖中的安全漏洞。 |
npm audit fix | 自动修复已知的漏洞。 |
npm run script-name | 运行 package.json 中定义的脚本,例如 npm run start。 |
npm start | 运行 start 脚本(等同于 npm run start)。 |
npm test | 运行 test 脚本。 |
npm build | 运行 build 脚本。 |
npm outdated | 列出项目中有可更新版本的依赖包。 |
npm version patch/minor/major | 更新 package.json 中的版本号,自动更新版本。 |
npm ci | 使用 package-lock.json 快速安装依赖,适用于 CI/CD 环境。 |
REPL(交互式解释器)
就是个给 JS 用的命令行。
回调函数
Node.js 的核心特性之一是其非阻塞 I/O(输入/输出)模型,这使得 Node.js 非常适合处理高并发的网络应用。
Node.js 异步编程的直接体现就是回调。
项目中创建一个文本文件 input.txt:
你好啊朋友!创建一个 main.js:
{% tabs callback %}
var fs = require('fs'); // fs 是 Node.js 提供的文件系统模块
var data = fs.readFileSync('input.txt', 'utf8');
console.log(data);
console.log("程序执行结束!");程序必须等待文件读取完成才会继续运行。
你好啊朋友!
程序执行结束!var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序执行结束!");程序会继续运行,文件读取完成后由回调函数显示文件内容。
程序执行结束!
你好啊朋友!{% endtabs %}
回调地狱(Callback Hell)
{% tabs callbackHell %}
当多个异步操作需要按顺序执行时,回调函数会导致代码嵌套,使得代码难以阅读和维护。
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) {
console.error('Error reading file1:', err);
return;
}
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) {
console.error('Error reading file2:', err);
return;
}
fs.readFile('file3.txt', 'utf8', (err, data3) => {
if (err) {
console.error('Error reading file3:', err);
return;
}
console.log('Data from all files:', data1, data2, data3);
});
});
});async/await 是 ES2017 引入的语法糖,可以让你更方便地处理异步操作,避免回调地狱。
const fs = require('fs').promises;
async function readFiles() {
try {
const data1 = await fs.readFile('file1.txt', 'utf8');
const data2 = await fs.readFile('file2.txt', 'utf8');
const data3 = await fs.readFile('file3.txt', 'utf8');
console.log('Data from all files:', data1, data2, data3);
} catch (err) {
console.error('Error reading files:', err);
}
}
readFiles();Promises 是另一种处理异步操作的方式,可以链式调用 then 方法,避免嵌套回调。
const fs = require('fs').promises;
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log('Data from file1:', data1);
return fs.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log('Data from file2:', data2);
return fs.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log('Data from file3:', data3);
})
.catch(err => {
console.error('Error reading files:', err);
});{% endtabs %}
事件循环
事件驱动程序
在 Node.js 中,事件驱动编程主要通过 EventEmitter 类来实现。
EventEmitter 是一个内置类,位于 events 模块中,通过继承 EventEmitter,你可以创建自己的事件发射器,并注册和触发事件。
Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例:
const EventEmitter = require('events');
// 创建事件触发器
const myEmitter = new EventEmitter();
// 监听事件
myEmitter.on('greet', () => {
console.log('Hello World!');
});
// 触发事件
myEmitter.emit('greet');另一个实例:
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
// 创建事件处理程序
var connectHandler = function connected() {
console.log('连接成功。');
// 触发 data_received 事件
eventEmitter.emit('data_received');
}
// 绑定 connection 事件处理程序
eventEmitter.on('connection', connectHandler);
// 使用匿名函数绑定 data_received 事件
eventEmitter.on('data_received', function(){
console.log('数据接收成功。');
});
// 触发 connection 事件
eventEmitter.emit('connection');
console.log("程序执行完毕。");EventEmitter
events 模块只提供了一个对象: events.EventEmitter。
EventEmitter 的核心就是事件触发与事件监听器功能的封装。
var events = require('events');
var eventEmitter = new events.EventEmitter();
// 监听器 #1
var listener1 = function listener1() {
console.log('监听器 listener1 执行。');
}
// 监听器 #2
var listener2 = function listener2() {
console.log('监听器 listener2 执行。');
}
// 绑定 connection 事件,处理函数为 listener1
eventEmitter.addListener('connection', listener1);
// 绑定 connection 事件,处理函数为 listener2
eventEmitter.on('connection', listener2);
var eventListeners = eventEmitter.listenerCount('connection');
console.log(eventListeners + " 个监听器监听连接事件。");
// 处理 connection 事件
eventEmitter.emit('connection');
// 移除监绑定的 listener1 函数
eventEmitter.removeListener('connection', listener1);
console.log("listener1 不再受监听。");
// 触发连接事件
eventEmitter.emit('connection');
eventListeners = eventEmitter.listenerCount('connection');
console.log(eventListeners + " 个监听器监听连接事件。");
console.log("程序执行完毕。");error 事件
EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,我们在遇到异常的时候通常会触发 error 事件。
当 error 被触发时,EventEmitter 规定如果没有响应的监听器,Node.js 会把它当作异常,退出程序并输出错误信息。
var events = require('events');
var emitter = new events.EventEmitter();
emitter.emit('error'); 继承 EventEmitter
大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它,包括 fs、net、http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。
Buffer(缓冲区)
Buffer 类在处理文件操作、网络通信、图像处理等场景中特别有用。
特性:
- 二进制数据:
Buffer对象是一个包含原始二进制数据的固定大小的数组。每个元素占用一个字节(8 位),因此Buffer适合处理二进制数据,如文件内容、网络数据包等。 - 不可变性:虽然
Buffer对象的内容可以在创建后修改,但其长度是固定的,不能动态改变。
buf = Buffer.alloc(26);
for (var i = 0; i < 26; i++) {
buf[i] = i + 97;
}
console.log(buf); // 输出: <Buffer 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a>
console.log(buf.toString('ascii')); // 输出: abcdefghijklmnopqrstuvwxyz
console.log(buf.toString('ascii',0,5)); // 使用 'ascii' 编码, 并输出: abcde
console.log(buf.toString('utf8',0,5)); // 使用 'utf8' 编码, 并输出: abcde
console.log(buf.toString(undefined,0,5)); // 使用默认的 'utf8' 编码, 并输出: abcdeStream(流)
Node.js 的 Stream 是一种处理流式数据的抽象接口,广泛应用于文件操作、网络通信等场景。
就是其它语言里操作文件的相关函数了。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
- data - 当有数据可读时触发。
- end - 没有更多的数据可读时触发。
- error - 在接收和写入过程中发生错误时触发。
- finish - 所有数据已被写入到底层系统时触发。
{% tabs callbackHell %}
var fs = require("fs");
var data = '';
// 创建可读流
var readerStream = fs.createReadStream('input.txt');
// 设置编码为 utf8。
readerStream.setEncoding('UTF8');
// 处理流事件 --> data, end, and error
readerStream.on('data', function(chunk) {
data += chunk;
});
readerStream.on('end',function(){
console.log(data);
});
readerStream.on('error', function(err){
console.log(err.stack);
});
console.log("程序执行完毕");程序执行完毕
你好啊朋友!
我要把你做成一个玩偶!可写流用于将数据写入目的地,常见的可写流包括文件写入流和网络请求发送流。
var fs = require("fs");
var data = '菜鸟教程官网地址:www.runoob.com';
// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('output.txt');
// 使用 utf8 编码写入数据
writerStream.write(data,'UTF8');
// 标记文件末尾
writerStream.end();
// 处理流事件 --> finish、error
writerStream.on('finish', function() {
console.log("写入完成。");
});
writerStream.on('error', function(err){
console.log(err.stack);
});
console.log("程序执行完毕");双工流同时具有可读和可写的能力。
const net = require('net');
// 创建一个 TCP 服务器
const server = net.createServer((socket) => {
console.log('Client connected.');
// 读取客户端数据
socket.on('data', (data) => {
console.log('Received data:', data.toString());
});
// 向客户端发送数据
socket.write('Hello, Client!\n');
// 监听关闭事件
socket.on('end', () => {
console.log('Client disconnected.');
});
});
server.listen(3000, () => {
console.log('Server listening on port 3000.');
});转换流是一种特殊的双工流,可以修改或转换数据。常见的转换流包括压缩和解压缩流。
const zlib = require('zlib');
const fs = require('fs');
// 创建一个可读流
const readableStream = fs.createReadStream('example.txt');
// 创建一个转换流(压缩)
const gzip = zlib.createGzip();
// 创建一个可写流
const writableStream = fs.createWriteStream('example.txt.gz');
// 将可读流管道到转换流,再管道到可写流
readableStream.pipe(gzip).pipe(writableStream);
// 监听完成事件
writableStream.on('finish', () => {
console.log('File compressed successfully.');
});管道提供了一个输出流到输入流的机制。
通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
var fs = require("fs");
// 创建一个可读流
var readerStream = fs.createReadStream('input.txt');
// 创建一个可写流
var writerStream = fs.createWriteStream('output.txt');
// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
readerStream.pipe(writerStream);
console.log("程序执行完毕");链式是通过连接输出流到另外一个流并创建多个流操作链的机制。
链式流一般用于管道操作。
压缩:
var fs = require("fs");
var zlib = require('zlib');
// 压缩 input.txt 文件为 input.txt.gz
fs.createReadStream('input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('input.txt.gz'));
console.log("文件压缩完成。");
解压:
var fs = require("fs");
var zlib = require('zlib');
// 解压 input.txt.gz 文件为 input.txt
fs.createReadStream('input.txt.gz')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('input.txt'));
console.log("文件解压完成。");可读流可以暂停和恢复数据的读取。
const fs = require('fs');
const readableStream = fs.createReadStream('example.txt', 'utf8');
readableStream.on('data', (chunk) => {
console.log('Received chunk:', chunk);
readableStream.pause(); // 暂停读取
setTimeout(() => {
readableStream.resume(); // 恢复读取
}, 1000);
});可以销毁流,释放资源。
const fs = require('fs');
const readableStream = fs.createReadStream('example.txt', 'utf8');
readableStream.on('data', (chunk) => {
console.log('Received chunk:', chunk);
readableStream.destroy(); // 销毁流
});{% endtabs %}
模块系统
Node.js 支持以下几种模块:
-
内置模块:Node.js 自带的模块,如
fs、http、path等。- 内置模块是由 Node.js 自带的模块,在安装 Node.js 时就已经包含在环境中,因此无需额外安装。常见的内置模块包括
fs、http、path、os、crypto等。
- 内置模块是由 Node.js 自带的模块,在安装 Node.js 时就已经包含在环境中,因此无需额外安装。常见的内置模块包括
-
用户自定义模块:由开发者创建的模块。
- 第三方模块是开发者或开源社区发布的模块,可以通过 npm(Node 包管理器)安装到项目中,常见的第三方模块有
express、lodash、axios等。
- 第三方模块是开发者或开源社区发布的模块,可以通过 npm(Node 包管理器)安装到项目中,常见的第三方模块有
-
第三方模块:通过 npm 安装的模块,如
express、lodash等。-
1、导出模块:使用
module.exports或exports将函数、对象或变量导出。 -
2、导入模块:使用
require()导入模块。
-
自定义模块导出与导入(CommonJS 模块)
{% tabs CommonJS %}
var Hello = require('./hello');
var hello = new Hello();
hello.setName("古尔丹");
hello.sayHello();Hello 古尔丹
// hello.js
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;{% endtabs %}
自定义模块导出与导入(ES 模块)
ES 模块使用 import 和 export,是现代 JavaScript 的模块规范。
- ES 模块使用
import和export关键字,需将文件扩展名设置为.mjs,或者在package.json中声明"type": "module"。 - ES 模块支持静态导入(
import ... from ...)和动态导入(import())。
{% tabs ES %}
// main.mjs
import { greet } from './myModule.mjs';
console.log(greet('Bob')); // 输出:Hello, Bob!node main.mjs
Hello, Bob!// myModule.mjs
export function greet(name) {
return `Hello, ${name}!`;
}{% endtabs %}
函数
箭头函数
ES6 引入的简洁函数表达式。
const greet = (name) => {
console.log(`Hello, ${name}!`);
};
// 单行箭头函数
const greet = name => console.log(`Hello, ${name}!`);路由
在 Node.js 中,路由是处理 HTTP 请求的关键部分,它决定了如何根据不同的 URL 和 HTTP 方法(如 GET、POST、PUT、DELETE 等)来分发请求。
路由通常用于构建 Web 应用程序,特别是 RESTful API。
Node.js 本身并没有内置的路由机制,但可以通过中间件库(如 Express)来实现。
路由通常涉及以下几个方面:
- URL 匹配:根据请求的 URL 来匹配路由规则。
- HTTP 方法匹配:根据请求的 HTTP 方法(GET、POST、PUT、DELETE 等)来匹配路由规则。
- 请求处理:一旦匹配到合适的路由规则,就调用相应的处理函数来处理请求。
创建一个简单的路由:
{% tabs routing %}
const http = require('http');
const fs = require('fs');
const path = require('path');
// 创建服务器并定义路由
const server = http.createServer((req, res) => {
const { url, method } = req;
if (url === '/' && method === 'GET') {
// 读取并返回 index.html 文件
fs.readFile(path.join(__dirname, 'index.html'), 'utf8', (err, data) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('500 Internal Server Error');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
}
});
} else if (url === '/about' && method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('About Page');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Not Found');
}
});
server.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>打得不错!</p>
</body>
</html>{% endtabs %}
node main.js
Server is running on http://localhost:3000
请求参数
一个完整 URL 的 http://localhost:8888/start?foo=bar&hello=world 包含主机、路径和查询字符串。
为了解析这些数据,我们可以使用 URL 对象和 querystring 模块。
const myUrl = new URL("http://localhost:8888/start?foo=bar&hello=world");
// 提取路径名
console.log(myUrl.pathname); // 输出: /start
// 提取查询参数
console.log(myUrl.searchParams.get("foo")); // 输出: bar
console.log(myUrl.searchParams.get("hello")); // 输出: world myUrl.pathname
|
|
-----
http://localhost:8888/start?foo=bar&hello=world
--- -----
| |
| |
myUrl.searchParams.get("foo") |
|
myUrl.searchParams.get("hello"){% tabs routing2 %}
var server = require("./server");
var router = require("./router");
server.start(router.route);// server.js
const http = require("http"); // 引入 Node.js 的 http 模块,用于创建服务器
const { URL } = require("url"); // 从 url 模块引入 URL 构造函数
// 定义并导出 start 函数,用于启动服务器
function start(route) {
// 定义 onRequest 函数,处理每个请求
function onRequest(request, response) {
// 使用 URL 构造函数解析请求路径
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname;
console.log(`Request for ${pathname} received.`); // 打印请求路径
route(pathname); // 调用路由函数处理路径
// 设置响应头和响应内容
response.writeHead(200, { "Content-Type": "text/plain" });
response.write("Hello World");
response.end();
}
// 创建服务器并监听指定端口
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}
// 导出 start 函数供其他模块使用
module.exports.start = start;function route(pathname) {
console.log("About to route a request for " + pathname);
}
// 导出了 route 函数
exports.route = route;{% endtabs %}
node index.js
Server has started.
Request for / received.
About to route a request for /
Request for /favicon.ico received.
About to route a request for /favicon.ico使用 Express 进行路由
Express 是一个流行的 Node.js 框架,它提供了强大的路由功能。安装它!
npm install express使用它!
const express = require('express');
const app = express();
const port = 3000;
// 定义一个 GET 路由
app.get('/', (req, res) => {
res.send('Hello, World!');
});
// 定义一个 POST 路由
app.post('/submit', (req, res) => {
res.send('Form submitted!');
});
// 启动服务器
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});全局对象
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。
- Node.js 全局对象 | 菜鸟教程 要用自己查吧……
常用工具
const util = require('util');- Node.js 常用工具 | 菜鸟教程 要用自己查吧……
文件系统
const fs = require('fs');- Node.js 文件系统 | 菜鸟教程 要用自己查吧……
Node.js 应用
构建简单应用
通过 npm init 命令生成 package.json 文件,它包含了项目的配置信息。
运行以下命令并按提示填写信息(可以直接按回车跳过):
npm init -y把 package.json 文件修改为以下内容:
{
"name": "my-first-node-app",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {}
}创建应用入口文件:app.js 文件,这是应用的入口文件,用于设置服务器和处理请求。
const http = require('http');
// 创建服务器
const server = http.createServer((req, res) => {
// 设置 HTTP 响应的状态码和头信息
res.writeHead(200, {
// 设置内容类型为 HTML,并指定字符集为 UTF-8,这样中文不会乱码
'Content-Type': 'text/html; charset=utf-8'
});
// 发送响应体
res.end('<h1>Hello, World!</h1><p>这是我的第一个 Node.js 应用。</p>');
});
// 监听端口
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});运行这个程序!
npm start
> my-first-node-app@1.0.0 start
> node app.js
Server is running on http://localhost:3000GET/POST 请求
在很多场景中,我们的服务器都需要跟用户的浏览器打交道,如表单提交。
表单提交到服务器一般都使用 GET/POST 请求。
获取 GET 请求内容
由于 GET 请求直接被嵌入在路径中,URL 是完整的请求路径,包括了 ? 后面的部分,因此你可以手动解析后面的内容作为 GET 请求的参数。
const http = require('http');
const util = require('util');
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
// 使用 URL 构造函数解析请求的 URL
const myUrl = new URL(req.url, `http://${req.headers.host}`);
// 输出 URL 的各个部分
res.end(util.inspect({
href: myUrl.href,
origin: myUrl.origin,
protocol: myUrl.protocol,
host: myUrl.host,
hostname: myUrl.hostname,
port: myUrl.port,
pathname: myUrl.pathname,
search: myUrl.search,
searchParams: Object.fromEntries(myUrl.searchParams) // 将 searchParams 转为普通对象
}));
}).listen(3000);
console.log("Server is running at http://localhost:3000");在浏览器中访问 http://localhost:3000/user?name=菜鸟教程&url=www.runoob.com 然后查看返回结果:
{
href: 'http://localhost:3000/user?name=%E8%8F%9C%E9%B8%9F%E6%95%99%E7%A8%8B&url=www.runoob.com',
origin: 'http://localhost:3000',
protocol: 'http:',
host: 'localhost:3000',
hostname: 'localhost',
port: '3000',
pathname: '/user',
search: '?name=%E8%8F%9C%E9%B8%9F%E6%95%99%E7%A8%8B&url=www.runoob.com',
searchParams: { name: '菜鸟教程', url: 'www.runoob.com' }
}获取 URL 的参数
我们可以使用 url.parse 方法来解析 URL 中的参数,代码如下:
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
// 使用 URL 构造函数解析请求 URL
const myUrl = new URL(req.url, `http://${req.headers.host}`);
// 获取查询参数
const name = myUrl.searchParams.get("name");
const siteUrl = myUrl.searchParams.get("url");
res.write("网站名:" + (name || "未提供"));
res.write("\n");
res.write("网站 URL:" + (siteUrl || "未提供"));
res.end();
}).listen(3000);
console.log("Server is running at http://localhost:3000");网站名:菜鸟教程
网站 URL:www.runoob.com
获取 POST 请求内容
在 Node.js 中,处理 POST 请求通常需要通过 http 模块来接收请求体中的数据。**POST 请求数据不像 GET 请求那样包含在 URL 中,而是作为请求体发送。**因此,在 Node.js 中接收 POST 数据时,需要监听并处理 request 对象的 data 和 end 事件。
- 监听
data事件:当数据块到达服务器时,data事件触发,数据块作为回调的参数传递。 - 监听
end事件:当整个请求体接收完毕时,end事件触发,这时可以对完整的 POST 数据进行处理。
const http = require('http');
// 创建 HTTP 服务器
http.createServer((req, res) => {
// 检查请求方法是否为 POST
if (req.method === 'POST') {
let body = '';
// 监听 data 事件,逐块接收数据
req.on('data', (chunk) => {
body += chunk; // 累加接收到的数据块
});
// 监听 end 事件,数据接收完毕
req.on('end', () => {
// 输出接收到的 POST 数据
console.log('Received POST data:', body);
// 设置响应头和内容
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('POST data received successfully!');
});
} else {
// 非 POST 请求的处理
res.writeHead(405, { 'Content-Type': 'text/plain' });
res.end('Only POST requests are supported.');
}
}).listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});你可以使用 curl 命令来测试 POST 请求:
curl.exe -X POST -d "name=example&age=25" http://localhost:3000
POST data received successfully!处理 JSON 数据
如果 POST 请求发送的是 JSON 数据,可以在 req.on('end') 中将接收的数据解析为对象:
req.on('end', () => {
const parsedData = JSON.parse(body); // 将 JSON 字符串解析为对象
console.log('Received JSON data:', parsedData);
res.end('JSON data received successfully!');
});querystring 模块
querystring 模块用于处理 URL 查询字符串和 POST 请求的数据。
假设客户端发送的 POST 请求数据格式为 application/x-www-form-urlencoded(例如表单提交),数据形式类似于 name=example&age=25。在接收到数据后,可以使用 querystring.parse 方法将数据解析成对象。
var http = require('http');
var querystring = require('querystring');
var postHTML =
'<html><head><meta charset="utf-8"><title>菜鸟教程 Node.js 实例</title></head>' +
'<body>' +
'<form method="post">' +
'网站名: <input name="name"><br>' +
'网站 URL: <input name="url"><br>' +
'<input type="submit">' +
'</form>' +
'</body></html>';
http.createServer(function (req, res) {
var body = "";
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
// 解析参数
body = querystring.parse(body);
console.log(body);
// 设置响应头部信息及编码
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf8' });
if (body.name && body.url) { // 输出提交的数据
res.write("网站名:" + body.name);
res.write("<br>");
res.write("网站 URL:" + body.url);
} else { // 输出表单
res.write(postHTML);
}
res.end();
});
}).listen(3000);
工具模块
| 模块名 | 描述 |
|---|---|
| OS 模块 | 提供基本的系统操作函数。 |
| Path 模块 | 提供了处理和转换文件路径的工具。 |
| Net 模块 | 用于底层的网络通信。提供了服务端和客户端的的操作。 |
| DNS 模块 | 用于解析域名。 |
| Domain 模块 | 简化异步代码的异常处理,可以捕捉处理 try catch 无法捕捉的。 |
Web 模块
大多数 web 服务器都支持服务端的脚本语言(php、python、ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器。
目前最主流的三个 Web 服务器是 Apache、Nginx、IIS。
使用 Node 创建 Web 服务器
{% tabs web %}
var http = require('http');
var fs = require('fs');
var url = require('url');
// 创建服务器
http.createServer( function (request, response) {
// 解析请求,包括文件名
var pathname = url.parse(request.url).pathname;
// 输出请求的文件名
console.log("Request for " + pathname + " received.");
// 从文件系统中读取请求的文件内容
fs.readFile(pathname.slice(1), function (err, data) {
if (err) {
console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/html
response.writeHead(404, {'Content-Type': 'text/html'});
} else {
// HTTP 状态码: 200 : OK
// Content Type: text/html
response.writeHead(200, {'Content-Type': 'text/html'});
// 响应文件内容
response.write(data.toString());
}
// 发送响应数据
response.end();
});
}).listen(8080);
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h1>我的第一个标题</h1>
<p>我的第一个段落。</p>
</body>
</html>{% endtabs %}
node server.js
Server running at http://127.0.0.1:8080/
Request for /index.html received.使用 Node 创建 Web 客户端
Node 创建 Web 客户端需要引入 http 模块,创建 client.js 文件,代码如下所示:
var http = require('http');
// 用于请求的选项
var options = {
host: 'localhost',
port: '8080',
path: '/index.html'
};
// 处理响应的回调函数
var callback = function(response){
// 不断更新数据
var body = '';
response.on('data', function(data) {
body += data;
});
response.on('end', function() {
// 数据接收完成
console.log(body);
});
}
// 向服务端发送请求
var req = http.request(options, callback);
req.end();Express 框架
先安装 cnpm:
npm install -g cnpm
added 1 package in 14s安装 Express 并将其保存到依赖列表中,然后继续安装:
cnpm install express --save
cnpm install body-parser --save
cnpm install cookie-parser --save
cnpm install multer --save安装完后,我们可以查看下 express 使用的版本号:
cnpm list express
my-first-node-app@1.0.0 D:\XXX\NodeJS
├── express@4.21.2 -> .\node_modules\.store\express@4.21.2\node_modules\express
└─┬ multer@1.4.5-lts.1 -> .\node_modules\.store\multer@1.4.5-lts.1\node_modules\multer
└── express@4.21.2 deduped -> .\node_modules\.store\express@4.21.2\node_modules\express创建一个服务器:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World');
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})RESTful API
REST 即表述性状态传递(英文:Representational State Transfer,简称 REST)是 Roy Fielding 博士在 2000 年他的博士论文中提出来的一种软件架构风格。
多进程
Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。
Node 提供了 child_process 模块来创建子进程,方法有:
| 方法 | 说明 |
|---|---|
exec() | 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。 |
spawn() | 使用指定的命令行参数创建新进程。 |
fork() | spawn() 的特殊形式,用于在子进程中运行的模块,如 fork('./son.js') 相当于 spawn('node', ['./son.js']) 。与 spawn 方法不同的是,fork 会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。 |
exec() 方法
{% tabs exec %}
const fs = require('fs');
const child_process = require('child_process');
for (var i = 0; i < 3; i++) {
var workerProcess = child_process.exec('node support.js ' + i, function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: ' + error.code);
console.log('Signal received: ' + error.signal);
}
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
});
workerProcess.on('exit', function (code) {
console.log('子进程已退出,退出码 ' + code);
});
}console.log("进程 " + process.argv[2] + " 执行。");{% endtabs %}
node master.js
子进程已退出,退出码 0
子进程已退出,退出码 0
stdout: 进程 0 执行。
stderr:
stdout: 进程 1 执行。
stderr:
子进程已退出,退出码 0
stdout: 进程 2 执行。
stderr:spawn() 方法
const fs = require('fs');
const child_process = require('child_process');
for (var i = 0; i < 3; i++) {
var workerProcess = child_process.spawn('node', ['support.js', i]);
workerProcess.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
workerProcess.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
workerProcess.on('close', function (code) {
console.log('子进程已退出,退出码 ' + code);
});
}node master.js
stdout: 进程 0 执行。
stdout: 进程 1 执行。
子进程已退出,退出码 0
子进程已退出,退出码 0
stdout: 进程 2 执行。
子进程已退出,退出码 0fork() 方法
const fs = require('fs');
const child_process = require('child_process');
for (var i = 0; i < 3; i++) {
var worker_process = child_process.fork("support.js", [i]);
worker_process.on('close', function (code) {
console.log('子进程已退出,退出码 ' + code);
});
}node master.js
进程 0 执行。
进程 1 执行。
子进程已退出,退出码 0
子进程已退出,退出码 0
进程 2 执行。
子进程已退出,退出码 0JXcore 打包
JXcore 是一个支持多线程的 Node.js 发行版本,基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。
其他应用
EJS
安装 ejs:
npm install ejs使用:
node server.js # 这个命令可以启动服务器
node generate.js # 这个命令编译文件