提示
具体内容查看 Node 的 API 文档 (opens new window)
无论是前面的学习,还是官网文档中的案例(同时演示了 CJS/ESM 规范两种写法),都表明 Node 也在跟随着时代发展的脚步
# 使用环境 🤔
浏览器中执行(前端)
浏览器中可以借助 webpack 等工具实现对 CommonJS 代码的转换;
浏览器本身是支持 ESModule 代码;
<script src="./index.js" type="module"></script>
Node 环境中执行 ✍(后端)
乖乖的使用 CommonJS 规范吧 😂
实在不爽,使用 node v13.2.0 之后的版本,并进行如下操作:
{
// 在package.json加入
"type": "module"
}
# 一、Buffer 类
# 1、字节
Buffer 是 Node 中的一个全局的类,可以用于操作二进制数据。
writeFile 中的应用
const { writeFile } = require('fs')
const { Buffer } = require('node:buffer')
const data = new Uint8Array(Buffer.from('Hello Node.js'))
// 没有就创建该文件
writeFile('./bundle.txt', data, { flag: 'a' }, (err, data) => {
if (err) throw err
console.log('追加(append)写入成功')
})
我们通常会把 8 位的二进制 合并为一个单元(字节 byte),也就是:
1 byte = 8 bit
,例如:1 表示为 0000 0001
编程 与 字节
java 中:
int 类型是 4 个字节,long 类型是 8 个字节(可以表示的数值范围更大)
TCP 中:
其传输的就是 字节流,读取和写入都需要标明 字节个数
RGB 中:
其 rgb(255,255,255)在计算机上采用的是一个字节存储,例如:255 表示的是 1111 1111
图片中:
在 webpack 中,url-loader 中的 limit 值的单位是字节(bytes),例如:{ limit: 10 * 1024 }
常见的文本 txt、图片 png/jpg 等通常是 kb 为单位,其中
1 kb = 1024 byte
如果是音频、视频等那还会使用更大的单位 M,其中
1 M = 1024 kb = 1024 * 1024 byte
# 2、buffer
通常情况下,我们可以把 Buffer 看成是一个存储字节(byte)的数组。
提示
这些通过 Buffer 存储的二进制数据,可以通过查看 ASCII 码表转换为对应的 字符
值得注意的是,一个中文字符需要 3 个字节 才能完成存储
// buffer数据
[一个字节] ——> [11111111] ——> [ff]
// 示例1
// console.log(new Buffer('buffer')) 已弃用
console.log(Buffer.from('buffer')) // <Buffer 62 75 66 66 65 72>
// 示例2
const { readFile } = require('fs')
readFile('./data.txt', (err, data) => {
if (err) throw err
console.log(data) // <Buffer e6 95 b0 e6 8d ae> ——> 数据
})
# 3、编码解码
const { Buffer } = require('node:buffer')
// 编码
const buf = Buffer.from('buffer')
const buf = Buffer.from('buffer', 'base64')
// 解码
console.log(buf.toString())
console.log(buf.toString('base64'))
# 4、字节操作
我们可以通过 charCodeAt()
获取指定字符的 ASCII 码 (opens new window) (128 个)
进一步的,我们也可以使用
fromCharCode()
将 ASCII 码还原为 指定字符
const { Buffer } = require('node:buffer')
// 初始化 8个字节 大小的 buffer内存空间
const buf = Buffer.alloc(8)
// 编码
buf[0] = 97
buf[1] = 0x62
buf[2] = 'c'.charCodeAt()
console.log(buf) // <Buffer 61 62 63 00 00 00 00 00>
// 解码
console.log(buf.toString()) // abc
# 二、文件相关
# 1、event 模块
提示
对应 event 模块的学习,我们可以联想一下 🤔 eventButs 的使用
events 事件触发器
├─ EventEmitter 类
│ ├─ evemtNames() # 获取所有监听事件的名称
│ ├─ listeners(eventName) # 获取某个事件名称对应的监听器数组
│ ├─ listenerCount(eventName) # 获取某个事件名称对应的监听器个数
│ ├─ setMaxListeners(n) # 设置监听最大的监听个数(默认值 10)
| └─ ……
└─ ……
设置监听器:emitter.on 、emitter.once 、emitter.prependListener 、emitter.prependOnceListener
取消监听器:emitter.off 、emitter.removeAllListeners
request 对象中的应用
http 基于 stream,而 stream 基于 event 模块
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
// 代码逻辑
})
server.listen(6061, () => {
console.log('Server running at http://localhost:6061')
})
const EventEmitter = require('node:events')
const emitter = new EventEmitter()
// 开启监听事件
function fooFn(payload) {
console.log('监听的事件被触发')
console.log('获得回传的数据为:', payload)
}
emitter.on('foo', fooFn)
// ======
setTimeout(() => {
// 发射事件
emitter.emit('foo', { code: 1, msg: { user: 'lencamo', pwd: 666666 } })
// 取消监听事件
emitter.off('foo', fooFn)
}, 2000)
事件触发器 应用场景
以 get 请求为例,我们为了解决拿取猫眼数据时存在异步的问题,采用了回调的方式解决了问题。
下面,我们使用 Node 触发器来实现一下。
const http = require('http')
const url = require('url')
const https = require('https')
const EventEmitter = require('events')
// 创建web服务器
http
.createServer((req, res) => {
const urlobj = url.parse(req.url, true)
callbackName = urlobj.query.callback
res.writeHead(200, {
'content-type': 'application',
// 跨域问题解决方案:CORS头
'Access-Control-Allow-Origin': '*'
})
// 案例升级:
var event = null
switch (urlobj.pathname) {
case '/api/list':
// 创建event对象
event = new EventEmitter()
// 1、监听paly事件
event.on('play', (data) => {
res.end(data)
})
// 发起数据请求
http_get()
// http_get((data) => {
// res.end(data)
// })
break
default:
res.end('404')
break
}
res.end()
})
.listen(3000, () => {
console.log('Server running at http://localhost:3000')
})
function http_get() {
var data = ''
https.get(
`https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E5%8C%97%E4%BA%AC&ci=1&channelId=4`,
(res) => {
res.on('data', (chunk) => {
data += chunk
})
// 2、数据获取完后,触发play事件
res.on('end', () => {
// cb(data)
event.emit('play', data)
})
}
)
}
# 2、Stream 流 🎈
http 基于 stream,而 stream 基于 event 模块
http 模块的 Request 和 Response 对象是基于 Stream 流实现的
所有的 Stream 流 都是 EventEmitter 的实例
提示
我们可以使用 Stream 流对数据进行精细控制
stream 流
├─ API for stream consumers
│ ├─ Writable 流
│ ├─ Readable 流
│ ├─ Duplex and Transform 流
| └─ ……
└─ API for stream implementers
Stream 流是对连续字节的一种表现形式和抽象概念,是可读可写的。
常见的流类型有:
- Writable (opens new window) 流(例如: fs.createWriteStream() (opens new window))
- Readable (opens new window) 流(例如: fs.createReadStream() (opens new window))
- Duplex and Transform (opens new window) 流
# 3、Readable
fs.createReadStream()
:以流的方式读取文件内容
const { createReadStream } = require('fs')
const readStream = createReadStream('./data.txt', { encoding: 'utf8' })
// 常见的事件
readStream.on('data', (chunk) => {
console.log(chunk)
})
readStream.on('end', () => {
console.log('Finished reading file.')
})
readStream.on('error', (err) => {
console.error(err)
})
我们可以使用其 option 选项对流进行 精细控制:
如果 data.txt 的内容为:
12345数据abc----
,则依次打印的内容为:数、据、abc
const { createReadStream } = require('fs')
const readStream = createReadStream('./data.txt', {
start: 5, // 起始字节位置
end: 13, // 结束字节位置
highWaterMark: 3 // 每次读几个字节
})
// 常见的方法
readStream.on('data', (chunk) => {
console.log(chunk.toString())
readStream.pause()
setTimeout(() => {
readStream.resume()
}, 2000)
})
# 4、Writable
fs.createWriteStream()
:以流的方式写入文件内容
const { createWriteStream } = require('fs')
const writeStream = createWriteStream('./data.txt', { flags: 'a', autoClose: true })
// 常见的方法
writeStream.write('hello node!', (err) => {
console.log(err)
})
// writeStream.close()
writeStream.end('\n') // close的同时追加内容
// 常见的事件
writeStream.on('finish', (chunk) => {
console.log('写入完成')
})
writeStream.on('close', (chunk) => {
console.log('文件被关闭')
})
# 5、pipe 管道
有时候,往往存在一些文件的拷贝操作
- 传统方式
const { readFile, writeFile } = require('fs')
readFile('./data.txt', (err, data) => {
if (err) throw err
writeFile('./data-copy.txt', data, (err) => {
if (err) throw err
})
})
- 流的方式
const { createReadStream, createWriteStream } = require('fs')
const readStream = createReadStream('./data.txt', { encoding: 'utf8' })
const writeStream = createWriteStream('./data-copy.txt', { encoding: 'utf8' })
// 方式1:流
// readStream.on('data', (chunk) => {
// writeStream.write(chunk)
// })
// readStream.on('end', () => {
// writeStream.close()
// })
// 方式2:管道
readStream.pipe(writeStream)
# 三、其他
# 1、crypto 模块
crypto 模块的目的是为了提供通用的加密和哈希算法。常见的应用场景有:文件完整性校验、数据加密等等
加密后的结果格式通常有:
hex(十六进制数)、base64、binary(二进制数)等等
- 哈希算法(Hash)
常见的哈希算法有:MD5、sha1,默认生成的是一个 hash 值
const crypto = requrie('crypto')
// 加密前
const password = '123456'
// 1、采用md5算法
const md5 = crypto.createHash('md5')
const pwd_md5 = md5.updata(password).digest('hex') // 结果转换为了hex格式
// 2、采用sha1算法
const sha1 = crypto.createHash('sha1')
const pwd_sha1 = sha1.updata(password).digest('hex') // 结果转换为了hex格式
// 加密后
console.log(pwd_md5)
console.log(pwd_sha1)
- 随机数增强的哈希算法(Hmac)
常见的哈希加密算法有:MD5、sha、sha256
const crypto = requrie('crypto')
// 注意:加了一个🚩秘钥
const hmac = crypto.createHmac('sha256', 'secret-key')
hmac.update('My blog is deer-sir.cn')
console.log(hmac.digest('hex'))
- 对称加密算法(AES)
常见的对称加密算法:aes-128-cbc
// 1、封装加密 和 解密函数
function encrypt(key, iv, data) {
let dep = crypto.createCipheriv('aes-128-cbc', key, iv)
// 设置输入、输出格式
return dep.update(data, 'binary', 'hex') + dep.final('hex')
}
function decrypt(key, iv, cryted) {
crypted = Buffer.from(crypted, 'hex').toString('binary')
let dep = crypto.createDecipheriv('aes-128-cbc', key, iv)
// 返回utf8格式的数据
return dep.update(crypted, 'binary', 'utf8') + dep.final('utf8')
}
// 2、使用示例
// 16*8=128
let key = 'abcdef1234567890'
let iv = '12abc34567890def'
let data = 'lencamo'
let cryted = encrypt(key, iv, data)
console.log('加密结果为:', cryted)
let decrypted = decrypt(key, iv, cryted)
console.log('解密结果为:', decrypted)
# 2、zlib 压缩
图示:
- 数据传输
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
const gzip = zlib.createGzip()
http
.createServer((req, res) => {
// 可读流
const rs = fs.createReadStream('./1.txt')
// 为浏览器配置让其对打包的文件解压
res.writeHead(200,{'Content-Type':'application/x-javascript;charset=utf-8';'Content-Encoding':'gzip'})
// 要求浏览器先对文件进行压缩 pipe(gzip),然后显示数据
// res:本质上也是一个🌈可写流(ws)
rs.pipe(gzip).pipe(res)
res.end()
})
.listen(8080, () => {
console.log('Server running at http://localhost:8080')
})