提示

  具体内容查看 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、编码解码

文章:详解字符编码 (opens new window)

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 流是对连续字节的一种表现形式和抽象概念,是可读可写的。

  常见的流类型有:

# 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')
  })
更新于 : 8/7/2024, 2:16:31 PM