Js-RUNOOB-NodeJS

JavaScript 是世界上最好的语言!

资源

Node.js 教程

教程

  • 脚本模式:node helloworld.js。

  • 交互模式:跟 Python 差不多的交互方式。

shell
node
Welcome to Node.js v22.11.0.
Type ".help" for more information.
> console.log("Hello, world!")
Hello, world!
undefined
>

创建第一个应用

创建一个 server.js:

javascript
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/');

启动它!

shell
node server.js
Server running at http://127.0.0.1:8888/

客户端将收到 HTTP 响应,由 4 个部分组成:状态行、消息报头、空行和响应正文。响应正文内容如下:

html
<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:

shell
npm install express
 
added 66 packages in 3s
 
13 packages are looking for funding
  run `npm fund` for details

全局安装(后面加后缀 -g):

shell
npm install express -g  
 
added 66 packages in 888ms

NPM 提供了很多命令,可以使用 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:

plaintext
你好啊朋友!

创建一个 main.js:

{% tabs callback %}

javascript
var fs = require('fs');  // fs 是 Node.js 提供的文件系统模块
 
var data = fs.readFileSync('input.txt', 'utf8');
 
console.log(data);
console.log("程序执行结束!");

程序必须等待文件读取完成才会继续运行。

plaintext
你好啊朋友!
程序执行结束!
javascript
var fs = require("fs");
 
fs.readFile('input.txt', function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});
 
console.log("程序执行结束!");

程序会继续运行,文件读取完成后由回调函数显示文件内容。

plaintext
程序执行结束!
你好啊朋友!

{% endtabs %}

回调地狱(Callback Hell)

{% tabs callbackHell %}

当多个异步操作需要按顺序执行时,回调函数会导致代码嵌套,使得代码难以阅读和维护。

javascript
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 引入的语法糖,可以让你更方便地处理异步操作,避免回调地狱。

javascript
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 方法,避免嵌套回调。

javascript
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 类来绑定和监听事件,如下实例:

javascript
const EventEmitter = require('events');
 
// 创建事件触发器
const myEmitter = new EventEmitter();
 
// 监听事件
myEmitter.on('greet', () => {
    console.log('Hello World!');
});
 
// 触发事件
myEmitter.emit('greet');

另一个实例:

Mermaid
javascript
// 引入 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 的核心就是事件触发与事件监听器功能的封装。

javascript
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 会把它当作异常,退出程序并输出错误信息。

javascript
var events = require('events'); 
var emitter = new events.EventEmitter(); 
emitter.emit('error'); 

继承 EventEmitter

大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它,包括 fs、net、http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。

Buffer(缓冲区)

Buffer 类在处理文件操作、网络通信、图像处理等场景中特别有用。

特性:

  • 二进制数据:Buffer 对象是一个包含原始二进制数据的固定大小的数组。每个元素占用一个字节(8 位),因此 Buffer 适合处理二进制数据,如文件内容、网络数据包等。
  • 不可变性:虽然 Buffer 对象的内容可以在创建后修改,但其长度是固定的,不能动态改变。
javascript
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' 编码, 并输出: abcde

Stream(流)

Node.js 的 Stream 是一种处理流式数据的抽象接口,广泛应用于文件操作、网络通信等场景。

webp

就是其它语言里操作文件的相关函数了。

所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:

  • data - 当有数据可读时触发。
  • end - 没有更多的数据可读时触发。
  • error - 在接收和写入过程中发生错误时触发。
  • finish - 所有数据已被写入到底层系统时触发。

{% tabs callbackHell %}

javascript
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("程序执行完毕");
plaintext
程序执行完毕
你好啊朋友!
我要把你做成一个玩偶!

可写流用于将数据写入目的地,常见的可写流包括文件写入流和网络请求发送流。

javascript
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("程序执行完毕");

双工流同时具有可读和可写的能力。

javascript
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.');
});

转换流是一种特殊的双工流,可以修改或转换数据。常见的转换流包括压缩和解压缩流。

javascript
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.');
});

管道提供了一个输出流到输入流的机制。

通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

javascript
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("程序执行完毕");

链式是通过连接输出流到另外一个流并创建多个流操作链的机制。

链式流一般用于管道操作。

压缩:

javascript
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("文件压缩完成。");
webp

解压:

javascript
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("文件解压完成。");

可读流可以暂停和恢复数据的读取。

javascript
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);
});

可以销毁流,释放资源。

javascript
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 等。
  • 用户自定义模块:由开发者创建的模块。

    • 第三方模块是开发者或开源社区发布的模块,可以通过 npm(Node 包管理器)安装到项目中,常见的第三方模块有 express、lodash、axios 等。
  • 第三方模块:通过 npm 安装的模块,如 express、lodash 等。

    • 1、导出模块:使用 module.exports 或 exports 将函数、对象或变量导出。

    • 2、导入模块:使用 require() 导入模块。

Mermaid

自定义模块导出与导入(CommonJS 模块)

{% tabs CommonJS %}

javascript
var Hello = require('./hello');
 
var hello = new Hello();
hello.setName("古尔丹");
hello.sayHello();
Hello 古尔丹
javascript
// 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 %}

javascript
// main.mjs
import { greet } from './myModule.mjs';
 
console.log(greet('Bob')); // 输出:Hello, Bob!
shell
node main.mjs
Hello, Bob!
javascript
// myModule.mjs
export function greet(name) {
    return `Hello, ${name}!`;
}

{% endtabs %}

函数

箭头函数

ES6 引入的简洁函数表达式。

javascript
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 %}

javascript
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');
});
html
<!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 %}

shell
node main.js
Server is running on http://localhost:3000
webp

请求参数

一个完整 URL 的 http://localhost:8888/start?foo=bar&hello=world 包含主机、路径和查询字符串。

为了解析这些数据,我们可以使用 URL 对象和 querystring 模块。

javascript
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
plaintext
                myUrl.pathname
                         |
                         |
                       -----
http://localhost:8888/start?foo=bar&hello=world
                                ---       -----
                                 |          |
                                 |          |
           myUrl.searchParams.get("foo")    |
                                            |
                                     myUrl.searchParams.get("hello")

{% tabs routing2 %}

javascript
var server = require("./server");
var router = require("./router");
 
server.start(router.route);
javascript
// 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;
javascript
function route(pathname) {
    console.log("About to route a request for " + pathname);
}
 
// 导出了 route 函数
exports.route = route;

{% endtabs %}

javascript
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 框架,它提供了强大的路由功能。安装它!

shell
npm install express

使用它!

javascript
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),它及其所有属性都可以在程序的任何地方访问,即全局变量。

常用工具

javascript
const util = require('util');

文件系统

javascript
const fs = require('fs');

Node.js 应用

构建简单应用

通过 npm init 命令生成 package.json 文件,它包含了项目的配置信息。

运行以下命令并按提示填写信息(可以直接按回车跳过):

shell
npm init -y

把 package.json 文件修改为以下内容:

json
{
  "name": "my-first-node-app",
  "version": "1.0.0",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {}
}

创建应用入口文件:app.js 文件,这是应用的入口文件,用于设置服务器和处理请求。

javascript
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}`);
});

运行这个程序!

shell
npm start
 
> my-first-node-app@1.0.0 start
> node app.js
 
Server is running on http://localhost:3000

GET/POST 请求

在很多场景中,我们的服务器都需要跟用户的浏览器打交道,如表单提交。

表单提交到服务器一般都使用 GET/POST 请求。

获取 GET 请求内容

由于 GET 请求直接被嵌入在路径中,URL 是完整的请求路径,包括了 ? 后面的部分,因此你可以手动解析后面的内容作为 GET 请求的参数。

javascript
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 然后查看返回结果:

plaintext
{
  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 中的参数,代码如下:

javascript
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 数据进行处理。
javascript
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 请求:

shell
curl.exe -X POST -d "name=example&age=25" http://localhost:3000
POST data received successfully!

处理 JSON 数据

如果 POST 请求发送的是 JSON 数据,可以在 req.on('end') 中将接收的数据解析为对象:

javascript
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 方法将数据解析成对象。

javascript
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);
webp

工具模块

模块名描述
OS 模块提供基本的系统操作函数。
Path 模块提供了处理和转换文件路径的工具。
Net 模块用于底层的网络通信。提供了服务端和客户端的的操作。
DNS 模块用于解析域名。
Domain 模块简化异步代码的异常处理,可以捕捉处理 try catch 无法捕捉的。

Web 模块

大多数 web 服务器都支持服务端的脚本语言(php、python、ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器。

目前最主流的三个 Web 服务器是 Apache、Nginx、IIS。

使用 Node 创建 Web 服务器

{% tabs web %}

javascript
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/');
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
    <h1>我的第一个标题</h1>
    <p>我的第一个段落。</p>
</body>
</html>

{% endtabs %}

webp
shell
node server.js
Server running at http://127.0.0.1:8080/
Request for /index.html received.

使用 Node 创建 Web 客户端

Node 创建 Web 客户端需要引入 http 模块,创建 client.js 文件,代码如下所示:

javascript
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:

shell
npm install -g cnpm                           
 
added 1 package in 14s

安装 Express 并将其保存到依赖列表中,然后继续安装:

shell
cnpm install express --save
cnpm install body-parser --save
cnpm install cookie-parser --save
cnpm install multer --save

安装完后,我们可以查看下 express 使用的版本号:

shell
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

创建一个服务器:

javascript
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 %}

javascript
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);
   });
}
javascript
console.log("进程 " + process.argv[2] + " 执行。");

{% endtabs %}

shell
node master.js
子进程已退出,退出码 0
子进程已退出,退出码 0
stdout: 进程 0 执行。
 
stderr:
stdout: 进程 1 执行。
 
stderr:
子进程已退出,退出码 0
stdout: 进程 2 执行。
 
stderr:

spawn() 方法

javascript
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);
   });
}
shell
node master.js
stdout: 进程 0 执行。
 
stdout: 进程 1 执行。
 
子进程已退出,退出码 0
子进程已退出,退出码 0
stdout: 进程 2 执行。
 
子进程已退出,退出码 0

fork() 方法

javascript
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);
   });
}
shell
node master.js
进程 0 执行。
进程 1 执行。
子进程已退出,退出码 0
子进程已退出,退出码 0
进程 2 执行。
子进程已退出,退出码 0

JXcore 打包

JXcore 是一个支持多线程的 Node.js 发行版本,基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。

其他应用

EJS

安装 ejs:

shell
npm install ejs

使用:

shell
node server.js  # 这个命令可以启动服务器
node generate.js  # 这个命令编译文件