node.js笔记

发布于:2025-03-24 ⋅ 阅读:(27) ⋅ 点赞:(0)

1. Node.js基本概念

1.1 什么是Node.js

Node.js是一个开源、跨平台的JavaScript运行环境,广泛应用于各类项目。它基于Google Chrome的V8 JavaScript引擎,性能卓越。

Node.js在单个进程中运行,利用异步I/O操作避免阻塞,能高效处理数千个并发连接,无需管理复杂的线程并发。

此外,Node.js让前端开发者能使用熟悉的JavaScript编写服务器端代码,无需学习新语言。

简单来说,浏览器是 javaScript 的前端运行环境,Node.js 是 JavaScript 的后端运行环境

 官方网站:Node.js — 在任何地方运行 JavaScript (nodejs.org)


1.2 Node.js对比浏览器

对比 node 浏览器
运行环境 JS的服务器端运行环境

JS的客户端运行环境。允许JS与HTML和CSS结合,实现网页的动态效果和交互功能

全局对象 global window
模块系统

同时支持 CommonJS 和 ES 模块系统

原生只支持ES模块系统,但可借助根据扩展

API 提供了用于服务器端操作的API,如fs、http、crypto (加密)、path (路径处理)等

提供了与网页交互的API,如DOM、XMR和fetch、requestAnimationFrame(动画)等

安全性 运行在服务器,通常无严格安全限制,可自由访问文件系统、监听网络端口等 直接与用户交互,因此有严格的安全限制,如同源策略、内容安全策略等,以保护用户免受恶意网站的攻击
事件循环

基于事件循环的非阻塞异步I/O机制。主要处理I/O操作、定时器、网络请求等任务

同样基于事件循环,但主要处理GUI渲染、用户交互、网络请求等任务
二进制数据处理

使用Buffer或流(Stream)来处理二进制数据

提供Blob API用于操作大型二进制对象,如文件

应用场景 适用于服务器端编程,如构建API、处理数据库操作、文件操作等。也适用于实时通信应用,如WebSocket 适用于前端编程,如构建用户界面、处理用户交互等

1.3 Node.js包管理器

npm 是 Node.js 的标准包管理器,是专门为Node.js设计的JS模块(或称为包)管理工具,它依赖于Node.js运行环境来执行命令并管理。

npm是全球最大的开源库生态系统,它提供了一个包含数百万个开源包的在线注册表,可供开发者下载和使用这些包,如Webpack、axios、Gulp......

npm使得安装、管理和共享代码包变得非常容易。前端开发人员可以利用npm来安装和管理前端依赖项,如React、Vue等框架和库。这使得它成为前端 JavaScript 中常用的使用工具

除了npm,还有pnpm,yarn,cnpm等前端 JS 常用的包管理器基本都依赖Node.js环境来运行,这也是为什么很多时候,我们明明是在进行以浏览器作环境的开发,却会安装node的原因。

现在安装Node.js时,npm通常会作为默认组件一起被安装(但可能需要自己手动配置npm环境变量)

npm常用指令:

指令 作用
npm init  初始化一个新的npm项目,并引导用户创建一个包含了项目基本信息和依赖项的package.json文件
npm install 安装项目所需的依赖包。参数:
  • <package-name>:安装指定名称的包。

  • --save或-S:添加到package.json文件的dependencies列表中(npm 5.0之后版本默认行为,可省略

  • --save-dev或-D:添加到package.json文件的devDependencies列表中,表示该包仅用于开发环境。

  • --global或-g:全局安装该包,而不是安装在当前项目中。注意,一般只有工具性质的包才有全局安装的必要性。因为它们提供了好用的终端命令。项目包会被安装到项目的 node_modules 目录中,全局包默认会被安装到系统的隐藏文件夹C:Users\用户目录\AppData\Roaming\npm\node_modules 目录下。

  • @<版本号>:安装指定版本的包。

注意:如果不携带参数,则会根据package.json文件中记录的所有依赖包的名称和版本号来一次性安装所有依赖包

npm uninstall 卸载已安装的依赖包。参数:
  • <package-name>:卸载指定的包。

  • --save:同时从package.json文件中删除该依赖项。(如果省略此参数,那些全局安装的依赖项,则不会从package.json文件中被删除)
npm update

更新指定包到最新版本。      参数:<package-name>:更新指定的包。

npm outdated 检查包是否已经过时,列出所有已经过时的包,以便及时进行包的更新
npm list

以树状结构列出当前项目中安装的所有依赖包及其版本。

npm root

查看当前包的安装路径。        参数:-g:查看全局包的安装路径

npm -v 查看npm安装的版本
npm cache clean

清理npm缓存

npm login 登录npm仓库,输入用户名和密码
npm whoami 查看当前登录的npm用户名
npm publish 发布包到npm仓库
npm help 查看npm的帮助信息。          参数:<command>:查看指定命令的详细帮助信息

1.4 Node.js相关配置

命令行查看是否已安装配置: node  -v

如果没有安装,参考网上相关博客,如:博主讲解的很详细


1.5 Node.js应用示例

const { createServer } = require('node:http');

const hostname = '127.0.0.1';
const port = 3000;

const server = createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

将以上代码以.js文件保存,并在终端使用 node \路径\文件名.js 指令运行,如:


2. Node.js模块化开发

2.1 基本概念

模块化开发是一种将程序的不同部分划分为独立模块的开发方式,每个模块都包含实现特定功能的代码。默认情况下面这个模块里面的方法或者属性,外面是没法访问的,这有助于避免命名冲突和数据污染。如果要让外部可以访问模块内的属性方法,就必须在模块内部将它们显示导出。

模块系统 CommonJS 和 ES ,两者都是JS中主流的模块系统,但它们之间存在一些显著的区别。

CommonJS ES
语法差异

使用require()函数导入模块。

使用module.exports或exports对象 导出模块成员。

使用import语句导入模块。

使用export语句导出模块成员。

模块加载时机

模块运行时加载,即代码执行到require()语句时,才会去加载对应的模块。

这种运行时加载,支持动态地导入和导出模块成员。

模块编译时加载,即在代码被JS引擎执行之前,模块的依赖关系已经被确定。

这种静态加载,支持Tree Shaking(移除未引用的代码)等优化技术。

模块缓存

模块在第一次加载时会被缓存,后续再次加载时会直接返回缓存的版本。

这意味着模块的初始化代码只会执行一次。

不会缓存,每次导入时都重新执行模块内的导出语句和初始化代码。

因此,当模块内部的值改变时,外部依赖该模块的模块总是能得到最新的值。

异步与同步

require()是同步加载的,所以在加载模块时,会阻塞后续代码的执行。

这种方式适合在服务器端使用,因为服务器端的资源都在本地,加载速度很快。

在浏览器环境中,import是异步加载的,这有助于提高性能,因为浏览器可以并行加载多个模块。

在Node.js中,虽然ES模块的import语句在语法上是异步的,但实际上Node.js会同步地解析和加载所有模块依赖,以确保模块系统的稳定性和可预测性。不过,这种同步加载是在编译时完成的,而不是在运行时。

适用环境

主要应用于Node.js生态系统,是Node.js默认的模块系统。

由于其同步加载的特性,不适合在浏览器环境中使用

是ECMAScript官方标准的一部分,被现代浏览器和JS运行时所支持。

顶层await 不支持顶层await,需要在async函数内部使用 支持在模块顶层使用await关键字,等待Promise解析

2.2 自定义模块

①  CommoJS模块开发

文件结构

文件代码 

//moduleA.js文件
function getName() {
    return 'moduleA'
}
module.exports = { getName }
//moduleB.js文件
function getName() {
    return 'moduleB'
}
exports = { getName }
//index.js文件
var { A_getName } = require(`./modules/moduleA.js`)
var moduleB = require(`./modules/moduleB.js`)

console.log(A_getName())
console.log(moduleB.getName())

在PowerShell中运行 文件

② ES模块开发,开启ES模块开发的方法有两种(node.js默认的CommoJS模块开发方式)

1. 最简单的方法是直接把你的 JS 文件扩展名从 .js 改为 .mjs。Node.js 会自动将这些文件视为 ES 模块。例如,将 app.js 改为 app.mjs。

2. 在你的 package.json 文件中添加或修改 "type" 字段为 "module"。这样,Node.js 会默认将 .js 文件视为 ES 模块。如下

在PowerShell中运行npm init - y指令,它初始化npm项目并创建一个带有默认值的package.json文件,这个文件会记录包名和下载安装第三方包信息的文件。

然后在package.json写入"type": “module”,这样就开启ES模块化了

{
  "name": "web_study",
  "version": "1.0.0",
  "description": "study-test",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "xuetao",
  "license": "MIT"
}

node中的ES模块开发就和平时在浏览器中的模块化开发语法一样

//moduleA.js文件
function getName() {
    return 'moduleB'
}
export default { getName }
//moduleB.js文件
export function getName() {
    return 'moduleB'
}
// 或
//export default { getName}

//index.js文件
import moduleA from './modules/moduleA.js'
import { getName } from './modules/moduleB.js'
//或
//import moduleB from './modules/moduleB.js'

3. 内置模块

浏览器提供了诸如DOM、fetch(网络请求)等与网页交互的API。而Node.js 则提供了诸多用于服务器端操作的API,如fs(文件系统)、http(网络请求)、crypto (加密)、path (路径处理)等。

3.1 Buffer模块

Buffer模块是Node.js中的一个全局变量。它是一段固定长度(Buffer对象创建后大小不可更改的连续内存空间,专门用于存储二进制数据。Buffer对象类似于数组,但它的元素都是二进制数据。

Buffer以字节为单位申请空间,1字节 = 8个二进制数位 = 十进制256-1 ,即每个元素的值在0到255之间

① Buffer.alloc(),创建一个指定大小的、被填满的Buffer实例。实例不会包含旧的和潜在的数据。

Buffer.alloc(size, fill[可选], encoding[可选])
size:Buffer的大小,单位字节
fill(可选):填充Buffer的值。默认用0填充
encoding(可选):如果fill是一个字符串,则该参数指定了字符串的编码。默认'utf8'
-------------------------------------------------------------------------
const buf1 = Buffer.alloc(10) // 创建一个10字节的Buffer,默认0填充
console.log(buf1) // <Buffer 00 00 00 00 00 00 00 00 00 00>

const buf2 = Buffer.alloc(10, 'a') // 创建一个10字节的Buffer,并用'a'的ASCII码值填充
console.log(buf2) //<Buffer 61 61 61 61 61 61 61 61 61 61>
console.log(buf2.toString()) // aaaaaaaaaa

const buf3 = Buffer.alloc(3, 'hello', 'utf8') // 'hello'被截断,因为Buffer只有3字节
console.log(buf3) // <Buffer 68 65 6c>
console.log(buf3.toString()) // hel

② Buffer.allocUnsafe(),创建一个指定大小的、未初始化的Buffer实例。这个方法比alloc()快,但返回的Buffer实例可能包含旧数据(因为它并不会像alloc()一样对内存旧数据进行清空,申请的内存中可能还留有之前的数据),因此在使用前需要特别注意。

Buffer.allocUnsafe(size)
size:单位字节
---------------------------------------------------------
const buf = Buffer.allocUnsafe(10); // 创建一个10字节的Buffer,但内容未初始化  
console.log(buf); // 可能包含旧数据,如<Buffer 78 01 00 00 00 00 00 00 00 00>  
  
// 在使用前,应该明确设置Buffer的内容  
buf.fill(0); // 用0填充Buffer  
console.log(buf); // <Buffer 00 00 00 00 00 00 00 00 00 00>

③ Buffer.from(),根据提供的字符串,数组,Buffer,ArrayBuffer 或 TypedArray创建一个新的Buffer实例。

1. 从字符串创建,编码格式,默认utf8
Buffer.from(str)
str:字符串

const buf = Buffer.from('aaaaaaaaaa')
console.log(buf)   //<Buffer 61 61 61 61 61 61 61 61 61 61>
------------------------------------------------------------------
2. 从数组创建
Buffer.from(array)

const buf = Buffer.from([0x74, 0x65, 0x73, 0x74])
console.log(buf) // <Buffer 74 65 73 74>
console.log(buf.toString()) // test

④ Buffer.toString(),根据指定的字符编码将Buffer解码成字符串。

Buffer.toString(encoding[可选], start[可选], end[可选]):
encoding(可选):字符编码。默认为'utf8'
start(可选):开始解码的字节偏移量。默认为0
end(可选):结束解码(但不包括)的字节偏移量。默认为Buffer.length
--------------------------------------------------------------------------
const buf = Buffer.from('test')
console.log(buf.toString()) // test
console.log(buf.toString('utf8', 2, 3)) // s

3.2 fs模块

fs(File System 模块)提供了一套用于与文件系统进行交互的 API。这些 API 允许你进行文件读取、写入、监视、创建目录等操作。fs 模块提供的 API 大致可以分为同步和异步两类。

导入fs模块

const fs = require('fs')

3.21 异步方法

异步方法不会阻塞事件循环,允许 Node.js 在等待文件系统操作完成时继续执行其他代码。异步方法更适合于 I/O 密集型操作,因为它们不会阻塞 Node.js 的事件循环。

这些方法通常有一个回调函数作为最后一个参数,当操作完成时调用该函数。

①  fs.readFile(),读取文件内容。

fs.readFileSync(path,options[可选],callback)
path:文件路径
options(可选):编码或标志
callback:回调函数,包含两个参数 (err, data)
--------------------------------------------
fs.readFile('./file/文本.txt', 'utf8', (err, data) => {
    //如果出错  则抛出错误
    if (err) throw err
    // 如果写了英文,会出现乱码,需要加一个toString();
    console.log(data.toString())
})

注意:如果不选options,该方法默认将文件内容读取为 Buffer 对象。

② fs.writeFile(),如果文件已存在,默认直接重写文件。如果不存在,则创建新文件。

fs.writeFile(path, data, options[可选], callback)
data:要写入的数据。
options(可选):编码或标志。
callback:回调函数,包含一个参数 err。
-----------------------------------------------------------
let data = 'Hello, World!'
fs.writeFile('./file/文本.txt', data, 'utf8', (err) => {
    if (err) throw err
    //如果写入成功,即没有出错, err === null
})
//使用 flag: 'a' 可将其指定为追加内容,和fs.appendFile()效果一样
fs.writeFile('./file/文本.txt', data, { encoding: 'utf8', flag: 'a' }, (err) => {
    if (err) throw err
})

③ fs.appendFile(),追加数据到文件末尾,如果文件不存在则创建文件。

fs.appendFile(path, data, options[可选], callback)
参数与 fs.writeFile 相同。
--------------------------------------------------
fs.appendFile('./file/文本.txt', data, 'utf8', (err) => {
    if (err) throw err
})

④ fs.unlink(), 删除文件。

fs.unlink(path,callback)
callback:回调函数,包含一个参数 err。
----------------------------------------------
fs.unlink('./file/文本.txt', (err) => {
    if (err) throw err
})

⑤ fs.stat(),检测是文件还是目录

fs.stat(path, callback)
-----------------------------------------------------
fs.stat('./file/文本.txt', (err, data) => {
    if (err) throw err
    if (data.isFile()) console.log('是文件')
    else if (data.isDirectory()) console.log('是目录')
})

⑥ fs.mkdir(),创建目录。

fs.mkdir(path, options[可选], callback)
options(可选):文件模式(权限)或递归选项。
callback:回调函数,包含一个参数 err。
------------------------------------------------
fs.mkdir('./file/m', (err) => {
    if (err) throw err
})

⑦ fs.rmdir(),删除目录。

fs.rmdir(path, options[可选], callback)
options(可选):递归选项。
callback:回调函数,包含一个参数 err。
----------------------------------------
fs.rmdir('./file/m', (err) => {
    if (err) throw err
})

 ⑧ fs.readdir(),读取目录内容。

fs.readdir(path,options[可选], callback)
options(可选):编码或文件类型。
callback:回调函数,包含两个参数 (err, files)
-------------------------------------------------------
fs.readdir('./file', (err, files) => {
    if (err) throw err
    console.log(files)
})

⑨ rename(), 重命名文件或移动文件位置

fs.rename((oldPath, newPath)
oldPath:原路径
newPath:新路径/新名字
-----------------------------------------------------------
fs.rename('./file/文本.txt', './file/text.txt', (err) => {
    if (err) throw err
    console.log('文件重命名成功')
})
fs.rename('./file/text.txt', './newFile/文本.txt', (err) => {
    if (err) throw err
    console.log('移动文件并重命名成功')
})

⑩ fs.watch(),监视文件或目录的更改。

fs.watch(filename,options[可选],listener)
filename:文件或目录路径。
options(可选):监听选项。
listener:回调函数,包含两个参数 (eventType, filename)
---------------------------------------------------------
const listener = (eventType, filename) => {
    if (filename) {
        console.log(`文件 ${filename} 发生了 ${eventType} 事件`)
    } else {
        // 监听的是目录时,filename 可能是 undefined,
        //此时 eventType 会是 'rename' 或 'change'
        console.log(`目录发生了 ${eventType} 事件`)
    }
}
// 调用 fs.watch() 方法
const watcher = fs.watch('./file/文本.txt', listener)

// 可选:监听额外的文件系统事件
watcher.on('change', (eventType, filename) => {
    console.log(`文件 ${filename} 发生了 change 事件`)
})
watcher.on('rename', (oldPath, newPath) => {
    console.log(`文件从 ${oldPath} 重命名为 ${newPath}`)
})

// 可选:停止监视(例如,在程序结束前)
// watcher.close()

3.22 同步方法

同步方法会阻塞事件循环,直到操作完成。这些方法没有回调函数,而是直接返回结果或抛出错误。同步方法通常用于简单的脚本或启动脚本,因为它们易于编写和理解,但在高并发或性能敏感的应用中应谨慎使用。

这些方法除了没有回调函数,参数与异步对应方法基本一样

① fs.readFileSync(path, options[可选]),同步读取文件内容,返回文件数据

try {
    const data = fs.readFileSync('./file/text.txt', 'utf8')
    console.log('同步读取文件内容:', data)
} catch (err) {
    console.error('读取文件失败:', err)
}

注意:如果不选options,该方法默认将文件内容读取为 Buffer 对象。

②fs.writeFileSync(path, data,options[可选]),同步写入数据到文件。

try {
    fs.writeFileSync('./file/text.txt', '这是要写入文件的内容', 'utf8')
    console.log('文件写入成功')
} catch (err) {
    console.error('写入文件失败:', err)
}

③fs.appendFileSync(path, data, options[可选]),同步追加数据到文件末尾


try {
    fs.appendFileSync('./file/text.txt', '这是要追加的内容', 'utf8')
    console.log('追加成功')
} catch (err) {
    console.error('追加失败:', err)
}

④fs.unlinkSync(path),同步删除文件。

⑥fs.mkdirSync(path, options[可选]),同步创建目录。

⑦fs.rmdirSync(path, options[可选]),同步删除目录。

⑧fs.readdirSync(path, options[可选]),同步读取目录内容,返回文件列表。


3.3 Stream流

Stream流提供了一种高效的方式来处理大量数据,特别是那些不适合一次性加载到内存中的数据。Stream有四种类型:可读流、可写流、双工流和转换流。

① 可读流,数据的源头,可以从中读取数据。

可读流让开发者可以按需读取数据,而不需要一次性将整个数据集加载到内存中。对于大型文件数据的读取,拥有较高性能。

fs.createReadStream(path, options[可选对象])
path:文件路径
options(可选):
{
    encoding:编码格式。默认为 null,表示使用原始的 Buffer 对象
    highWaterMark:一次能读取的最大字节数。默认为 64 KB(65536 字节)
    autoClose:读完或出错时,是否自动关闭可读流。默认true
    start:指定文件的开始读取位置
    end:指定文件的结束读取位置
}
-----------------------------------------------------------------
const readable = fs.createReadStream('./file/text.txt')

// 监听data事件,读取文件中的数据块,并在控制台输出。
readable.on('data', (chunk) => {
    console.log(chunk)           //<Buffer e8 bf ........... more bytes>
    console.log(chunk.toString())//这是流式读取的第一行内容  这是流式读取的第二行内容
})
// 文件读取完毕,触发end事件。
readable.on('end', () => {
    console.log('Finished reading file.')
})
// 如果读取过程中发生错误,触发error事件。
readable.on('error', (err) => {
    console.error('Error reading file:', err)
})

注意:默认情况下,data 事件的回调接收的数据是 Buffer 对象。

② 可写流,数据的终点,可以向其中写入数据。

可写流让开发者可以将按需写入数据,不需要等待所有数据都准备好后再一次性写入或多次打开文件。可以提高数据写入的性能。

fs.createWriteStream(path, options[可选对象])
path:路径
optiopns:
{
    flags: 'w'(覆盖式写入,默认值), 'a'(追加写),'r+'(读写,文件必须存在)、'w+'(读写,覆盖文件)等。
    encoding:编码格式。默认为 null,表示写入原始的 Buffer 对象或字符串(根据 write 方法的参数类型)。你也可以将其设置为 'utf8'、'ascii' 或 'base64' 等编码格式。
    highWaterMark:一次能写入的最大字节数默认为 16 KB
    autoClose:是否自动关闭
}
----------------------------------------------------------------
const writable = fs.createWriteStream('./file/output.txt')

//write()方法, 在可写流中写入数据
writable.write('Hello, ')
writable.write('World!\n')

//end()方法,结束写入并关闭流(但可以写入最后的数据)
writable.end('This is the end.')

//写入流关闭,触发finish事件
writable.on('finish', () => {
    console.log('Finished writing to file.')
})
//如果发生错误,触发error事件
writable.on('error', (err) => {
    console.error('Error writing to file:', err)
})

⑤ 常用方法

(1)pipe():将一个可读流的数据发送到另一个可写流,用于大规模复制文件。

// 创建读取流
var readStream = fs.createReadStream('./file/text.txt')
// 创建写入流,指向不同的文件
var writeStream = fs.createWriteStream('./file/text_copy.txt')

// 把读取到的文件复制到写入流的路径当中去
readStream.pipe(writeStream)

writeStream.on('finish', function () {
    console.log('流式写入完成')
})

(2)pause():将可读流从流动模式切换到暂停模式;resume():从暂停模式切换到流动模式。

通常用于当你不想立即处理流中的所有数据时,或者需要控制数据读取的速率,防止处理速度跟不上数据读取速度,导致内存压力时。

const fs = require('fs')
// 每次读取16字节
const readable = fs.createReadStream('./file/text.txt', { highWaterMark: 16 })

readable.on('data', (chunk) => {
    console.log(`Received ${chunk.length} bytes of data.`)
})
// 暂停流
readable.pause()
// 一段时间后恢复流
setTimeout(() => {
    readable.resume()
}, 2000)

(3)read():手动从可读流中读取数据。 通常用于在可读流的暂停模式下,获取数据

const fs = require('fs')
const readable = fs.createReadStream('./file/text.txt')

// 暂停流
readable.pause()

readable.on('readable', () => {
    let chunk
    //每次读取20字节
    while (null !== (chunk = readable.read(20))) {
        console.log(`Received ${chunk.length} bytes of data.`)
        console.log(chunk.toString())
    }
})

readable.on('end', () => {
    console.log('There will be no more data in the readable.')
})

3.4 path模块

path 模块提供了一些用于处理文件路径和目录路径的实用API。这些API可以帮助你进行路径的拼接、解析、转换等操作,确保你的代码在不同的操作系统上都能正确处理文件路径。

常用于在处理文件路径时配合fs模块,来构建跨平台的路径。

导入path模块

const path = require('path')

① path.join(),把多个 path 片段拼接成一个完整的路径(它会使用平台特定的分隔符作为定界符规范化生成的路径,如' \ ')

//__dirname是Node.js中的一个全局变量,它返回当前执行脚本所在的目录的绝对路径
const fullPath = path.join(__dirname, 'some', 'directory', 'file.txt')
console.log(fullPath) 
//E:\STUDY\web_study\Server\some\directory\file.txt

② path.resolve(),路径拼接(绝对路径),即 将路径或路径片段的序列解析为绝对路径

const fullPath = path.resolve('some', 'directory', 'file.txt')
console.log(fullPath)
// E:\STUDY\web_study\Server\some\directory\file.txt

③ path.basename(),返回路径中的最后一部分。可选参数 ext 允许你指定一个文件扩展名,如果路径的最后一部分匹配这个扩展名,它会被移除。

let fullPath = `${__dirname}/file/text.txt`
// 输出文件名(包括扩展名)
console.log(path.basename(fullPath)) //text.txt
// 输出不包括.txt扩展名的文件名
console.log(path.basename(fullPath, '.txt')) //text

④ path.extname(path),返回路径path 中文件的扩展名部分。

⑤ path.dirname(path),返回路径path 中的目录名。

⑥ path.parse(path),将路径字符串解析为一个对象并返回,该对象包含路径的各个组成部分。如

{
  root: 'E:\\',
  dir: 'E:\\STUDY\\web_study\\Server/file',
  base: 'text.txt',
  ext: '.txt',
  name: 'text'
}

⑦path.format(pathObj),将路径对象转成路径字符串返回


3.5 url模块

url 模块提供了用于解析、构建、规范化和编码 URL 的实用工具函数。这些函数在处理网络请求、构建 API 路由或解析用户输入的 URL 时非常有用。

3.51 URL基本概念

URL(Uniform Resource Locator,统一资源定位符)是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。URL可以依据不同的划分标准进行划分,如下七部分划分法(详细格式):

URL的一般语法格式为:“protocol://hostname[:port]/path[;parameters][?query]#fragment”,其中带方括号的部分为可选项。具体组成如下:

  1. protocol(协议):传输协议,如HTTP、HTTPS等。
  2. hostname(域名):服务器的域名系统(DNS)主机名或IP地址。有时,在主机名前也可以包含连接到服务器所需的用户名和密码(username:password@hostname)
  3. port(端口):省略时使用方案的默认端口,各种传输协议都有默认的端口号,如HTTP的默认端口为80。有时候出于安全或其他考虑,可以在服务器上对端口进行重定义,即采用非标准端口号,此时URL中就不能省略端口号这一项。
  4. path(路径):用来表示主机上的一个目录或文件地址。
  5. parameters(参数):用于指定特殊参数的可选项,由服务器端程序自行解释
  6. query(查询字符串):用于给动态网页传递参数。可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开。
  7. fragment(锚点):用于指定网络资源中的片段。例如,一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。这部分从“#”开始到最后,都是锚部分。

3.52 url 常用方法

①  URL 类

  • Node.js v7.0.0 中引入,作为解析和构建 URL 的推荐方式。
  • URL 对象具有多个属性(如 href、protocol、hostname、port、pathname、search、hash 等)和方法(如 toString()、toJSON() 等)来访问和修改 URL 的各个部分。
  • 提供了 new URL(urlString[, base]) 构造函数来创建 URL 对象。

②  url.format(urlObject),根据URL 对象(或类似的对象)返回格式化后的 URL 字符串。

③ url.toFilePath(urlObject),将一个 URL 对象转换为一个文件路径。

使用示例:

const { URL } = require('url')

const myURL = new URL('https://example.com:8080/some/path?name=ferret#nose')

console.log(myURL.href) // 输出完整的 URL 字符串
console.log(myURL.protocol) // 输出 'https:'
console.log(myURL.hostname) // 输出 'example.com'
console.log(myURL.port) // 输出 '8080'
console.log(myURL.pathname) // 输出 '/some/path'
console.log(myURL.search) // 输出 '?name=ferret'
console.log(myURL.hash) // 输出 '#nose'

// 修改 URL 的某个部分
myURL.port = '3000'
console.log(myURL.href) // 输出修改后的 URL 字符串

注意:url.parse() 和 url.resolve()这两个方法已经在 Node.js v11.0.0 中被弃用,并在后续版本中移除。建议使用 URL 类代替。


3.5 http模块

HTTP模块在Node.js中是一个核心模块,它提供了创建HTTP服务器和HTTP客户端的能力。允许开发者创建高效的HTTP服务器。这个服务器是基于事件的,内部由C++实现,接口由JavaScript封装。 ..........前置知识,建议先学习了解HTTP协议,

可参考:前端网络请求库:Axios_前端axios请求-CSDN博客

1. HTTP服务器创建

//1. 导入http模块
const http = require('http')

//2. 创建一个新的HTTP服务器实例
const server = http.createServer()

//requestListener函数会在每次接收到请求时被调用
const requestListener = (req, res) => {
    //req(代表HTTP请求对象)和res(代表HTTP响应对象)
    res.statusCode = 200
    res.setHeader('Content-Type', 'text/plain')
    res.end('Hello World')
}
//3. 为服务器实例绑定request事件的处理函数
server.on('request', requestListener)

//4. 启动服务器
// server.listen(port, host, backlog, callback)

const port = 3000 //监听指定的端口
const hostname = '127.0.0.1' //监听指定的主机名
const listening = () => {
    //成功监听指定端口后,触发listening事件
    console.log(`Server running at http://${hostname}:${port}/`)
}

server.listen(port, hostname, listening)

以上示例和 ''1.5 Node.js应用示例'' 一样


2. HTTP请求对象(req)

Node.js中,每有客户端发送请求到服务器,服务器会生成一个HTTP请求对象(req)。这个对象包含了请求的所有信息,比如请求头、请求方法、URL、查询参数、请求体等。以下是一些对象常用属性和方法的简要介绍:

属性 描述
req.url 请求的URL地址
req.method 请求的方法类型(如GET、POST等)
req.headers 请求头信息,是一个包含所有请求头部的对象
req.body 请求体内容(通常用于POST请求),需要中间件(如body-parser或express.json())解析。

req.pipe(destination),将请求数据流式传输到目的地(如文件或另一个请求)。

req.pipe(fs.createWriteStream('output.txt'));

req.on(event, listener),监听请求事件(如'data'、'end'等)

req.on('data', (chunk) => {
    console.log(`Received ${chunk.length} bytes of data.`)
})
req.on('end', () => {
    console.log('No more data in request.')
})

简单示例:

const server = http.createServer((req, res) => {
    console.log(`Request method: ${req.method}`)
    console.log(`Request URL: ${req.url}`)
    console.log(`Request headers: ${JSON.stringify(req.headers)}`)

    // 假设只处理POST请求
    if (req.method === 'POST') {
        let body = ''
        req.on('data', (chunk) => {
            body += chunk.toString() // 拼接数据块
        })
        req.on('end', () => {
            console.log(`Request body: ${body}`)
            res.writeHead(200, { 'Content-Type': 'text/plain' })
            res.end('Request received successfully')
        })
    } else {
        res.writeHead(200, { 'Content-Type': 'text/plain' })
        res.end('Please send a POST request')
    }
})

3. HTTP响应对象(res)

Node.js中,当服务器接收到客户端的请求并处理完毕后,它会使用res对象来构造并发送响应回客户端。

①  res对象的主要职责大致包括以下部分:

  • 发送响应头:通过res.writeHead()或res.setHeader()方法设置响应的状态码、状态短语和响应头信息。

  • 发送响应体:通过res.write()方法发送响应内容,可以多次调用直到所有数据发送完毕。

  • 结束响应:通过res.end()方法结束响应,并可选地发送剩余的响应内容。

②  res对象的常用方法和属性

方法/属性 描述
res.writeHead(statusCode, statusMessage, headers)

设置响应头。statusCode是HTTP状态码。statusMessage是状态码的文本描述,如'OK'。headers是一个对象,包含要发送的响应头信息。

res.setHeader(name, value) 设置单个响应头字段。name是响应头的名称,value是响应头的值。
res.write(chunk, [encoding]) 发送响应内容的一部分。chunk是要发送的数据,可以是Buffer、字符串或Uint8Array。encoding是可选的,指定数据的编码方式,默认为'utf8'。
res.end([data], [encoding]) 结束响应。如果提供了data,则将其作为响应体的最后一部分发送。encoding是可选的,指定数据的编码方式。
res.statusCode 获取或设置HTTP状态码。

 简单示例:

const server = http.createServer((req, res) => {
    // 注意:设置响应头信息,防止中文乱码
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })
    res.write(`
        <html>
          <h1>hello h1 </h1>
          <h1>你好 一级标题 </h1>
        <html>
        `)
    res.end()
})

4. 外部模块

4.1 基本概念

这是Node.js中除自定义模块和内置模块外的第三类模块,也称为第三方模块或npm包,它们通常是由开发者社区创建的模块,可通过npm等包管理器进行安装和使用。这些模块提供了丰富的功能,使得开发者可以快速地构建和扩展应用程序,而不必从头开始编写所有代码。

① 初始化一个node.js项目,创建package.json文件。该文件会记录项目中安装的依赖包

//创建一个带有默认值的package.json文件
npm init -y 

② 使用npm安装各种外部模块。

npm install <packgeName>

安装成功后,会创建一个 node_modules 的文件夹和 package-lock.json文件

node_modules 文件夹:存放所有包(或称外部模块)。require() 会从该文件夹中查找并加载包。

package-lock.json 配置文件 :记录 node_modules 目录中每个包的下载信息(名字,版本号等)

③ 导入<packgeName>模块并使用,步骤和内置模块使用一致


4.2 Express模块

Express是一个基于Node.js平台的、 npm 上的第三方包,是一个快速、开放、极简的Web开发框架。作用与内置的http模块类似,但它基于HTTP模块进一步封装,提供了快速创建Web服务器的便捷方法,极大地提高了开发效率。官方网站:Express - Node.js Web

4.21 Express的基本使用

①命令行运行  npm install express 安装Express模块,然后导入模块并使用

// 1. 导入 express
const express = require('express')
// 2. 创建 web 服务器
const app = express()
// 3.设置路由(设置一个基本的路由)
app.get('/', (req, res) => {
    // 把响应内容发送给客户端
    res.send('Hello, World!')
})
// 4.  监听端口并启动服务器
const port = 3000
app.listen(port, () => {
    console.log(`Server is running at http://localhost:${port}`)
})

 ② 监听客户端请求的方式

app.get(path, callback)
app.post(path, callback)
app.put(path, callback)
app.delete(path, callback)
.....

path:请求路径
callback:处理请求的逻辑

Express服务器实例,通过上面这种方式来监听客户端的请求,并执行相应逻辑操作。如①中的app.get(),就是监听客户端对路径" http://localhost:127.0.0.1:3030/"的get请求,并通过处理函数向客户端发送响应。

③ 向客户端发送响应

app.get('/', (req, res) => {
    // 向客户端发送JSON数据
    res.send({ id: 0, goods: 'apple', sale: 3000 })
    // 或者
    const response = {
        message: 'This is a JSON response',
        status: 'success'
    }
    res.json(response)
})

通过 res.send() 方法,可以把处理好的内容,发送给客户端


4.22 Express的功能特性

1. 路由
  • 路由提供了一种简单的方式来定义和处理不同URL路径的请求。
  • 支持支持模块化路由,以保持代码的清晰和可维护性。

通过定义路由,将客户端的请求映射到特定的处理程序函数。如下,将路径/image的get请求映射到处理函数sendImg

cosnt sendImg = (req, res) => {
    res.send('imageBase64')
}

app.get('/image',sendImg)

不过上面这种方式,是把路由直接挂载到app上,不利于对路由进行维护和管理,所以Express 推荐将路由抽离为单独的模块。

① 创建路由模块对应的 .js 文件,完成配置后导出模块

var express = require('express') // 1. 导入express
var router = express.Router() // 2. 创建路由对象

router.get('/image/list', (req, res) => {// 3.挂载获取照片列表的路由
    res.send('Get image list')
})

router.post('/image/add', (req, res) => {// 4. 挂载添加图片的路由
    res.send('Add image image')
})

module.exports = router

②  使用 app.use() 函数注册路由模块

const imageRouter = require('./router/image.js')  //导入路由模块

app.use(imageRouter) // 给app注册路由模块

推荐:类似于托管静态资源时,为静态资源统一挂载访问前缀一样,给路由模块添加前缀
app.use('api', imageRouter)

2. 中间件
  • 中间件是可以在请求和响应之间执行功能的函数。它可以访问请求对象(req)、响应对象(res)和下一个中间件函数(next)
  • 多个中间件函数共享同一份 req  res
  • Express提供了内置的中间件,也允许自定义中间件。中间件可用于处理身份验证、日志记录、错误处理、请求体解析,添加、删除或修改 HTTP 头信息等多种任务。

全局挂载中间件 app.use() ,这是最常见的添加全局中间件的方法。客户端发起的任何请求,到达服务器之后,都会触发该中间件。

// 全局中间件,处理所有请求
app.use((req, res, next) => {
    console.log('请求时间:', Date.now())
    next() 
    //注意:在当前中间件的业务处理完毕后,必须调用 next()函数
    //表示把流转关系转交给下一个中间件或路由
})
app.get('/', (req, res) => {
    res.send('Hello, World!')
})

 局部注册中间件 app.METHOD() ,可以为特定 HTTP 方法(如 GET、POST)添加中间件,没注册的请求不会调用这个中间件函数。

const log = (req, res, next) => {
    console.log('请求时间:', Date.now())
    next() // 调用 next() 以传递控制权给下一个中间件
}
// 注册局部中间件
app.get('/', log, (req, res) => {
    res.send('Hello, World!')
})
// 下面这个请求,不会触发中间件log
app.get('/image/list', (req, res) => {
    res.send('image list')
})

③ 中间件数组,你可以将多个中间件组合成一个数组并传递给 app.use() 或 app.METHOD()。

const loggerMiddleware = (req, res, next) => {
    console.log('日志记录中间件')
    next()
}
const authMiddleware = (req, res, next) => {
    console.log('身份验证中间件')
    next()
}
app.use([loggerMiddleware, authMiddleware], (req, res) => {
    res.send('组合中间件示例')
})

④  错误处理中间件,错误处理中间件用于捕获和处理应用程序中的错误,它没有 next() 函数,并且通常放在所有其他中间件之后。

const errorHandler = (err, req, res, next) => {
    console.error(err.stack)
    res.status(500).send('服务器内部错误')
}

app.use(errorHandler)

总结几个中间的使用注意事项:

  1. 一定要在路由之前注册中间件
  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理
  3. 执行完中间件的业务代码之后,不要忘记调用 next()函数
  4. 为了防止代码逻辑混乱,调用 next()函数后不要再写额外的代码
  5. 连续调用多个中间件时,多个中间件之间,共享req 和 res 对象

3. 内置中间件

除了上面介绍的全局(应用级)中间件,局部(路由级)中间件和错误中间件,Express还提供了一些用于简化常见的任务的内置中间件。

接口测试推荐使用:Apifox

①  express.Router(),用于创建可挂载的路由器对象。通过它,可以实现路由的模块化管理

②  express.json(),用于将JSON格式的请求体解析成一个JS对象,并将解析后的数据附加到req.body上,通常用于处理POST请求中的JSON数据。

app.post('/json', express.json(), (req, res) => {
    console.log(req.body) // 输出:{ name: 'John Doe', age: 30 }
    res.send('JSON数据已接收')
})

 ③ express.urlencoded({ extended: true }),用于解析URL-encoded格式的请求体,并将解析后的数据附加到req.body上,它能够处理包含嵌套对象的表单数据。

如果去掉选项{ extended: true },就会采用较为简单解析算法,可能无法处理嵌套对象等复杂结构。但多数简单的嵌套情况依然能够正确解析。

app.post('/urlencoded', express.urlencoded({ extended: true }), (req, res) => {
    console.log(req.body)
//{ username:'admin',profile:{ name:'firefly',age:'18', adress:{ city:'Penocony'}}
    res.send('URL-encoded数据已接收')
})

④   express.static(),用于提供静态文件服务。在Web应用中,静态文件(如图片、CSS、JS文件等)通常不会改变,因此可以高效地从服务器提供给客户端。

// 设置静态文件目录为public  
app.use(express.static('public'))

// 现在你可以访问public目录下的文件了
// 例如,访问http://localhost:3000/images/logo.png将会返回public/images/logo.png   

// 还可托管多个静态目录
app.use(express.static('files'))
app.use(express.static('download'))

// 还可以在托管的静态资源访问路径之前添加一个路径前缀
app.use('/static', express.static('public'))

注意:public不会出现在路径中


4. 第三方中间件
  • 第三方模块和中间件可以进一步增强Express的功能。
  • 常用的包括日志输出(morgan)、跨域资源共享(cors)、文件上传(multer)等。
4.1 multer

multer ​​​​​​​是一个基于fs和stream模块的中间件,专门用于处理 multipart/form-data 类型的表单数据,这种类型的数据是文件上传的标准格式。

Multer在解析完请求体后,会向请求(req)对象中添加一个body对象和一个file或files对象(上传多个文件时使用files对象),其中body对象中包含所提交表单中的文本字段,而file(或files)对象中包含通过表单上传的文件。

Multer还提供了灵活的配置选项,允许开发者指定文件的存储位置、文件名、文件大小限制等。

①  安装指令:npm install multer

②  ​​​​​​引入并配置

const multer = require('multer')
const path = require('path')
const app = express()

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        const filePath = path.join(__dirname, 'file')
        cb(null, filePath) //指定文件储存的目录
    },
    filename: (req, file, cb) => {
    // 生成文件名,这里使用时间戳和文件扩展名
        cb(null, Date.now() + path.extname(file.originalname)) 
    }
})

const upload = multer({ storage: storage })

③  在Express应用中设置文件上传的路由,并使用Multer提供的中间件处理文件上传

// upload.single('file')表示期待一个名为file的字段包含上传的文件

app.post('/upload', upload.single('file'), (req, res) => {
    // req.file 包含上传的文件信息
    // req.body 包含表单中的文本字段(如果有)
    res.send('File uploaded successfully')
})

④  Multer的配置选项

dest或storage:指定文件的存储位置和文件名生成规则。可以使用multer.diskStorage()或multer.memoryStorage()来配置存储引擎。

diskStorage:磁盘存储是 Multer 的默认存储方式
memoryStorage:内存存储将上传的文件保存在内存中,
               在处理小型文件或需要快速访问文件数据的场景中非常有用。

filename:用于生成上传后的文件名。用于在storage中配置

filename: (req, file, cb) => {
      cb(null, 'name') 
}
req:请求对象
file:文件对象
cb:回调函数, 用于指定文件名。它接收两个参数
    第一个:错误对象(如果有错误发生)。如果没有错误,传递 null。
    第二个:你想要设置的文件名。
-------------------------------------------------------------
const storage = multer.diskStorage({
    filename: (req, file, cb) => {
        cb(null, Date.now() + path.extname(file.originalname)) 
    }
})

destination:用于指定文件存储的目录。用于在storage中配置

destination: (req, file, cb) => { 
    cb(null, 'uploads/')
}
参数情况和filename相同,除了
cb的第二个参数:用于指定储存目录
------------------------------------------------
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        const filePath = path.join(__dirname, 'file')
        cb(null, filePath) //指定文件储存的目录
    }
})

limits:设置文件上传的限制条件,如文件大小、文件数量等。通常是传递给multer()函数的对象

const upload = multer({  
  storage: storage,  
  limits: { 
       fileSize: 1024 * 1024 * 5 // 限制文件大小为5MB  
       files: 1, // 限制整个请求中的文件数量为1  
       fieldsSize: 1024 * 1024 * 50 // 限制整个请求中的字段大小为50MB
    }
})

fileFilter:用于控制可上传的文件类型。通常是传递给multer()函数的对象​​​​​​​

const upload = multer({  
  storage: storage,  
  fileFilter: (req, file, cb) => {  
    const fileTypes = /jpeg|jpg|png|gif/  // 允许上传的图片类型  
    const mimetype = file.mimetype       // 检查文件类型  
    if (mimetype && !mimetype.match(fileTypes)) {  
      return cb(new Error('Only JPEG, JPG, PNG and GIF files are allowed!'), false)
    }  
    cb(null, true)
    回调函数cb 接收两个参数:错误对象(如果有的话)和 表示是否接受该文件的布尔值
  }  
}); 

注意事项

在使用Multer处理文件上传时,需要确保指定的存储目录存在且可写。


4.2 morgan

morgan 是一个 HTTP 请求日志中间件,用于记录请求的详细信息,如请求方法、URL、状态码、响应时间等。

安装指令: npm install morgan

const express = require('express')
const morgan = require('morgan')
const app = express()

app.use(morgan('combined')) // 使用 'combined' 格式记录日志
app.get('/', (req, res) => {
    res.send('Hello World!')
})

5. 自定义中间件
// 自定义中间件示例:记录请求时间
function loggerMiddleware(req, res, next) {
    const startTime = Date.now()
    // 在响应发送后记录请求处理时间
    res.on('finish', () => {
        const endTime = Date.now()
        console.log(
            `Request to ${req.originalUrl} took ${endTime - startTime}ms`
        )
    })
    // 调用下一个中间件或路由处理函数
    next()
}
app.use(loggerMiddleware)

对于功能复杂的自定义中间件,推荐使用模块抽离封装


5. 跨域问题

5.1 基本概念

跨域问题是浏览器执行网络请求时,由于同源策略的限制,不能从其他 “源” (这里的源,就是协 议,域名,端口号的组合,同源就是这个组合必须相同)获取资源或执行脚本。这一策略旨在保护用户数据安全,防止恶意网站读取另一个网站的数据,但也给需要访问不同源资源的Web应用(尤其是在开发时)带来了挑战。

在Web服务器中,解决跨域问题,主要有种方法:

  1. CORS (主流的解决方案,推荐使用)
  2. JSONP (有缺陷的解决方案:只支持 GET 请求)

5.2 CORS

什么是CORS ?

CORS  ,跨域资源共享(Cross-Origin Resource Sharing)。它允许浏览器向跨源服务器发出请求,从而克服了AJAX只能同源使用的限制。

CORS由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。通过配置CORS相关的HTTP响应头,就可以解除浏览器默认的跨域访问限制。

使用中间件cors

cors 用于启用跨源资源共享(CORS),允许服务器接受来自不同源的请求。

安装指令:npm install cors

const cors = require('cors');  
const app = express();  
 
app.use(cors()); // 启用所有 CORS 请求  
手动配置响应头

除了使用中间件快速开启CORS,我们也可以手动配置响应头来实现CORS。

//all()方法,对所有类型的HTTP请求应用该中间件函数
app.all('*', (req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*') // 处理跨域
    res.header('Access-Control-Allow-Methods', 'POST,GET') // 只允许POST和GET
    next() // 执行下一个中间件
})

 ① CORS 响应头部 - Access-Control-Allow-Origin:

其中,origin 参数的值指定了允许访问该资源的外域 URL。

​​​​​​​'*'表示接受来自任何域的请求。也可以指定具体的域名:

res.header('Access-Control-Allow-Origin', 'http://127.0.0.1') 
// 只有来自http://127.0.0.1的请求才会被允许访问服务器资源

② CORS 响应头部 - Access-Control-Allow-Methods:

默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法

注意:对于复杂的CORS需求,建议使用CORS中间件,不仅可以简化CORS策略的管理,还可以减少因手动设置响应头而导致的错误。


5.3 JSONP 

概念:利用浏览器端通过<script>标签的 src 属性引用资源不受同源策略影响的特性,来请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做 JSONP.
特点:
① JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 这个对象。
② JSONP 仅支持 GET 请求,不支持 POST、PUT、DELETE 等请求。

客户端:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>JSONP Example</title>
        <script>
            function handleResponse(data) {
                // 我们将返回的对象转换为JSON字符串以显示在页面上
                var jsonString = JSON.stringify(data, null, 2)
                document.getElementById('result').innerText = jsonString
            }
            function loadJSONP() {
                var script = document.createElement('script') // 创建一个新的script元素

                // 设置script元素的src属性,包含callback参数
                script.src =
                    'http://127.0.0.1:3000/jsonp?callback=handleResponse'

                document.body.appendChild(script) // 将script元素添加到DOM中,这将触发对JSONP接口的请求
            }
        </script>
    </head>
    <body>
        <h1>JSONP Example</h1>
        <button onclick="loadJSONP()">Load JSONP Data</button>
        <pre id="result"></pre>
    </body>
</html>

服务器端:

app.get('/jsonp', (req, res) => {
    const callback = req.query.callback // 从请求中获取callback参数

    // 要返回的数据
    const data = {
        name: 'John Doe',
        age: 30,
        city: 'New York'
    }
    // 构造JSONP响应,将数据转换为字符串,并拼接到函数调用中
    const jsonpResponse = `${callback}(${JSON.stringify(data)})`

    // 设置响应头为text/javascript,因为JSONP本质上是一个<script>标签加载的JS代码
    res.setHeader('Content-Type', 'text/javascript')

    res.send(jsonpResponse) // 发送响应
})

注意:为了防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。


6. 模块相关补充

6.1 Crypto模块

Crypto模块提供了一组全面的加密和解密数据的功能,用于保护数据的机密性和完整性。主要用于保护敏感数据(如用户密码、支付信息等),验证数据的完整性和来源。

引入Crypto模块:

const crypto = require('crypto')
 1. 对称加密

加密和解密使用相同的密钥。使用步骤如下:

①  ​​​​​​​指定加密算法、密钥和初始化向量:加密算法如aes-128-cbcaes-192-cbcaes-256-cbc等;密钥和初始化向量(IV)需要是Buffer对象。

const algorithm = 'aes-192-cbc'
const key = crypto.randomBytes(24) // 生成24字节的随机密钥
const iv = crypto.randomBytes(16) // 生成16字节的随机初始化向量

② 创建加密对象,并加密数据

// 创建加密对象
const cipher = crypto.createCipheriv(algorithm, key, iv)

// 加密数据
let encrypted = cipher.update(message, 'utf8', 'hex')
encrypted += cipher.final('hex')

console.log('加密后的数据:', encrypted)
------------------------------------------
update 方法,逐步加密数据:
    message:要加密的原始数据
    'utf8':告诉update,message是以'utf8'格式编码的
    'hex':告诉update,加密后的数据块应该被编码为十六进制字符串

注意:update可能无法一次性处理完所有数据,比如:数据量较大或加密块大小有限时

final 方法,完成加密过程,并返回任何剩余的数据作为加密后的输出。
    该方法必须在所有数据加密完成后调用,以确保所有数据都被正确处理。
    调用final方法后,加密对象将不再可用,因为它已经完成了它的任务。

③ 创建解密对象,并解密数据

// 创建解密对象
const decipher = crypto.createDecipheriv(algorithm, key, iv)

// 解密数据
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')

console.log('解密后的数据:', decrypted)


2. 哈希算法

哈希算法是一种将任意长度的数据转换为固定长度摘要的算法。

注意:从哈希值几乎不可能反推出原始数据

用指定的哈希算法,创建哈希对象:如md5sha256sha512等。

const hash = crypto.createHash('md5')

更新哈希对象

const data = '123456'
hash.update(data)

③ 计算哈希值

const result = hash.digest('hex')

console.log('MD5哈希值:', result)
//MD5哈希值: e10adc3949ba59abbe56e057f20f883e

6.2 Node.js 事件

Node中事件基于事件驱动模型,使得Node.js能够高效地处理异步操作。

1. 核心概念
  1. 异步非阻塞:Node.js采用异步非阻塞的I/O操作方式,当执行I/O操作时(如文件读写、网络请求等),Node.js不会等待操作完成,而是继续执行后续的代码。当I/O操作完成时,会触发一个事件,Node.js通过事件回调机制来处理这个操作的结果。
  2. 单线程:Node.js运行在单线程上,这意味着它一次只能执行一个任务。但是,通过事件机制和异步I/O,Node.js能够高效地处理并发任务
  3. 事件驱动:Node.js的事件机制使得它能够在事件发生时触发相应的回调函数,从而处理该事件。这种机制使得Node.js能够灵活地响应各种事件,如用户请求、文件读写完成等。

2. 事件模块

Node.js中的事件模块是events模块,它提供了一个核心类EventEmitter,用于处理事件。EventEmitter类提供了一系列方法来添加、移除和触发事件监听器。

① 添加事件监听器

  • addListener(eventName, listener):为指定事件添加一个监听器。
  • on(eventName, listener):与addListener方法相同,也是为指定事件添加一个监听器。这是Node.js中推荐的方法。

② 移除事件监听器

  • removeListener(eventName, listener):移除指定事件的某个监听器。
  • removeAllListeners([eventName]):移除指定事件的所有监听器,如果未指定事件名称,则移除所有事件的所有监听器。

③ 触发事件

  • emit(eventName, [...args]):触发指定事件,并传递可选的参数给该事件的监听器。
3. Node.js中的常见事件类型

在Node.js中,常见的事件类型包括连接事件、关闭事件、错误事件、数据事件、结束事件、超时事件等。这些事件通常与I/O操作相关,如网络连接、文件读写等。

  1. 连接事件(connection):当一个新的客户端请求到达时触发。
  2. 关闭事件(close):当服务器或客户端连接关闭时触发。
  3. 错误事件(error):当发生错误时触发。
  4. 数据事件(data):当接收到数据时触发。
  5. 结束事件(end):当服务器或客户端发送完数据后触发。
  6. 超时事件(timeout):当连接超时时触发。