内置模块之 path

  • 内置模块, require 之后直接使用
  • 用于处理文件/目录的路径

path 模块常见 API

  • basename() 获取路径中基础名称
    • 返回的就是接收路径当中的最后一部分
  • dirname()获取路径中目录名称
    • 返回路径中最后一个部分的上一层目录所在路径
  • extname() 获取路径中扩展名称
    • 返回 path路径中相应文件的后缀名
    • 如果 path 路径当中存在多个点,它匹配的是最后一个点,到结尾的内容
  • isAbsolute() 获取路径是否为绝对路径
  • join()拼接多个路径片段
  • resolve() 返回绝对路径
  • parse() 解析路径
    • 接收一个路径,返回一个对象,包含不同的信息
    • root、dir、base、ext、name
  • format() 序列化路径
  • normalize() 规范化路径

全局变量之 Buffer

  • Buffer 让 JavaScript 可以操作二进制
  • Nodejs 中 Buffer 是一片内存空间
  • Buffer 是 Nodejs 的内置类
  • 总结
    • 无需 require 的一个全局变量
    • 实现 Nodejs 平台下的二进制数据操作
    • 不占据 V8 堆内存大小的内存空间
    • 内存的使用由 Node 来控制, 由 V8 的 GC 回收
    • 一般配合 Steam 流使用, 充当数据缓冲区
  • image.png

创建 Buffer 实例

  • alloc:创建指定字节大小的 buffer
  • allocUnsafe: 创建指定大小的 buffer (不安全)
  • from: 接收数据, 创建 buffer

Buffer 实例方法

  • fill: 使用数据填充 buffer
  • write:向 buffer 中写入数据
  • toString: 从buffer 中提取数据
  • slice: 截取 buffer
  • indexOf: 在 buffer 中查找数据
  • copy: 拷贝 buffer 中的数据


  1. let buf = Buffer.alloc(6)
  2. // fill
  3. /* buf.fill(123)
  4. console.log(buf)
  5. console.log(buf.toString()) */
  6. // write
  7. /* buf.write('123', 1, 4)
  8. console.log(buf)
  9. console.log(buf.toString()) */
  10. // toString
  11. /* buf = Buffer.from('拉勾教育')
  12. console.log(buf)
  13. console.log(buf.toString('utf-8', 3, 9)) */
  14. // slice
  15. /* buf = Buffer.from('拉勾教育')
  16. let b1 = buf.slice(-3)
  17. console.log(b1)
  18. console.log(b1.toString()) */
  19. // indexOf
  20. /* buf = Buffer.from('zce爱前端,爱拉勾教育,爱大家,我爱所有')
  21. console.log(buf)
  22. console.log(buf.indexOf('爱qc', 4)) */
  23. // copy
  24. let b1 = Buffer.alloc(6)
  25. let b2 = Buffer.from('拉勾')
  26. b2.copy(b1, 3, 3, 6)
  27. console.log(b1.toString())
  28. console.log(b2.toString())

Buffer 静态方法

  • concat: 将多个 buffer 拼接成一个新的 buffer
  • isBuffer: 判断当前数据是否为 Buffer ```typescript /* let b1 = Buffer.from(‘拉勾’) let b2 = Buffer.from(‘教育’)

let b = Buffer.concat([b1, b2], 9) console.log(b) console.log(b.toString()) 拉勾教 */

// isBuffer let b1 = ‘123’ console.log(Buffer.isBuffer(b1))

  1. <a name="QrPTV"></a>
  2. ### 自定义 Buffer 之 split
  3. ```typescript
  4. ArrayBuffer.prototype.split = function (sep) {
  5. let len = Buffer.from(sep).length
  6. let ret = []
  7. let start = 0
  8. let offset = 0
  9. while( offset = this.indexOf(sep, start) !== -1) {
  10. ret.push(this.slice(start, offset))
  11. start = offset + len
  12. }
  13. ret.push(this.slice(start))
  14. return ret
  15. }
  16. let buf = 'zce吃馒头,吃面条,我吃所有吃'
  17. let bufArr = buf.split('吃')
  18. console.log(bufArr)

FS 模块

  • FS 是内置核心模块, 提供文件系统操作的 API
  • image.png

常见 flag 操作符

  • r: 表示可读
  • w: 表示可写
  • s:表示同步
  • +:表示执行相反操作
  • x:表示排它操作
  • a:表示追加操作


fd 就是操作系统分配给被打开文件的标识


fs 介绍总结

  • fs 是 Nodejs 中内置核心模块
  • 代码层面上 fs 分为基本操作类和常用 API
  • 权限位、标识符、操作符

    文件读写与拷贝操作

    文件操作 API

  • readFile: 从指定文件中读取数据

  • writeFile: 向指定文件中写入数据
    • 会覆盖原文件内容
    • 如果没有指定文件、会自动创建一个
  • appendFile: 追加的方式向指定文件中写入数据
  • copyFile: 将某个文件中的数据拷贝至另一文件
  • watchFile: 对指定文件进行监控
  • **unwatchFile: 取消监控

**

  1. const fs = require('fs')
  2. const path = require('path')
  3. // readFile
  4. /* fs.readFile(path.resolve('data1.txt'), 'utf-8', (err, data) => {
  5. console.log(err)
  6. if (!null) {
  7. console.log(data)
  8. }
  9. }) */
  10. // writeFile
  11. /* fs.writeFile('data.txt', '123', {
  12. mode: 438,
  13. flag: 'w+',
  14. encoding: 'utf-8'
  15. }, (err) => {
  16. if (!err) {
  17. fs.readFile('data.txt', 'utf-8', (err, data) => {
  18. console.log(data)
  19. })
  20. }
  21. }) */
  22. // appendFile
  23. /* fs.appendFile('data.txt', 'hello node.js',{}, (err) => {
  24. console.log('写入成功')
  25. }) */
  26. // copyFile, 第一个是被拷贝文件 第二个是拷贝文件
  27. /* fs.copyFile('data.txt', 'test.txt', () => {
  28. console.log('拷贝成功')
  29. }) */
  30. // watchFile
  31. fs.watchFile('data.txt', {interval: 20}, (curr, prev) => {
  32. // curr 表示修改之后的文件 prev 表示修改之前的文件
  33. if (curr.mtime !== prev.mtime) {
  34. console.log('文件被修改了')
  35. fs.unwatchFile('data.txt')
  36. }
  37. })

文件操作实现 md 转 html

  1. const fs = require('fs')
  2. const path = require('path')
  3. const marked = require('marked')
  4. const browserSync = require('browser-sync')
  5. /**
  6. * 01 读取 md 和 css 内容
  7. * 02 将上述读取出来的内容替换占位符,生成一个最终需要展的 Html 字符串
  8. * 03 将上述的 Html 字符写入到指定的 Html 文件中
  9. * 04 监听 md 文档内容的变经,然后更新 html 内容
  10. * 05 使用 browser-sync 来实时显示 Html 内容
  11. */
  12. let mdPath = path.join(__dirname, process.argv[2])
  13. let cssPath = path.resolve('github.css')
  14. let htmlPath = mdPath.replace(path.extname(mdPath), '.html')
  15. fs.watchFile(mdPath, (curr, prev) => {
  16. if (curr.mtime !== prev.mtime) {
  17. fs.readFile(mdPath, 'utf-8', (err, data) => {
  18. // 将 md--》html
  19. let htmlStr = marked(data)
  20. fs.readFile(cssPath, 'utf-8', (err, data) => {
  21. let retHtml = temp.replace('{{content}}', htmlStr).replace('{{style}}', data)
  22. // 将上述的内容写入到指定的 html 文件中,用于在浏览器里进行展示
  23. fs.writeFile(htmlPath, retHtml, (err) => {
  24. console.log('html 生成成功了')
  25. })
  26. })
  27. })
  28. }
  29. })
  30. browserSync.init({
  31. browser: '',
  32. server: __dirname,
  33. watch: true,
  34. index: path.basename(htmlPath)
  35. })
  36. const temp = `
  37. <!DOCTYPE html>
  38. <html lang="en">
  39. <head>
  40. <meta charset="UTF-8">
  41. <title></title>
  42. <style>
  43. .markdown-body {
  44. box-sizing: border-box;
  45. min-width: 200px;
  46. max-width: 1000px;
  47. margin: 0 auto;
  48. padding: 45px;
  49. }
  50. @media (max-width: 750px) {
  51. .markdown-body {
  52. padding: 15px;
  53. }
  54. }
  55. {{style}}
  56. </style>
  57. </head>
  58. <body>
  59. <div class="markdown-body">
  60. {{content}}
  61. </div>
  62. </body>
  63. </html>
  64. `

文件打开与关闭

  • fs.open
  • fs.close ```typescript const fs = require(‘fs’) const path = require(‘path’)

// open fs.open(path.resolve(‘data.txt’), ‘r’, (err, fd) => { console.log(fd) })

// close fs.open(‘data.txt’, ‘r’, (err, fd) => { console.log(fd) fs.close(fd, err => { console.log(‘关闭成功’) }) })

  1. <a name="nKbwD"></a>
  2. ### 大文件读写操作
  3. - ![image.png](https://cdn.nlark.com/yuque/0/2022/png/245377/1659970846744-fa8f0f01-063a-4541-a30a-9edfad78deab.png#clientId=u38ebfc24-b645-4&from=paste&height=448&id=ue6c4ba22&originHeight=895&originWidth=1589&originalType=binary&ratio=1&rotation=0&showTitle=false&size=160483&status=done&style=none&taskId=uf3dabb86-583a-4fa8-b690-291dc68fb8f&title=&width=794.5)
  4. - 一次性读写大文件时,内存有限,通过Buffer 搬运
  5. - 读: 把磁盘里的内容读出来,放到Buffer 暂存区中
  6. - 写: 把Buffer 暂存区中的内容写入到磁盘文件中
  7. ```typescript
  8. const fs = require('fs')
  9. // read : 所谓的读操作就是将数据从磁盘文件中写入到 buffer 中
  10. let buf = Buffer.alloc(10)
  11. /**
  12. * fd 定位当前被打开的文件
  13. * buf 用于表示当前缓冲区
  14. * offset 表示当前从 buf 的哪个位置开始执行写入
  15. * length 表示当前次写入的长度
  16. * position 表示当前从文件的哪个位置开始读取
  17. */
  18. /* fs.open('data.txt', 'r', (err, rfd) => {
  19. console.log(rfd)
  20. fs.read(rfd, buf, 1, 4, 3, (err, readBytes, data) => {
  21. console.log(readBytes)
  22. console.log(data)
  23. console.log(data.toString())
  24. })
  25. }) */
  26. // write 将缓冲区里的内容写入到磁盘文件中
  27. buf = Buffer.from('1234567890')
  28. fs.open('b.txt', 'w', (err, wfd) => {
  29. fs.write(wfd, buf, 2, 4, 0, (err, written, buffer) => {
  30. console.log(written, '----')
  31. fs.close(wfd)
  32. })
  33. })

文件拷贝自定义实现

  1. const fs = require('fs')
  2. /**
  3. * 01 打开 a 文件,利用 read 将数据保存到 buffer 暂存起来
  4. * 02 打开 b 文件,利用 write 将 buffer 中数据写入到 b 文件中
  5. */
  6. let buf = Buffer.alloc(10)
  7. // 01 打开指定的文件
  8. /* fs.open('a.txt', 'r', (err, rfd) => {
  9. // 03 打开 b 文件,用于执行数据写入操作
  10. fs.open('b.txt', 'w', (err, wfd) => {
  11. // 02 从打开的文件中读取数据
  12. fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
  13. // 04 将 buffer 中的数据写入到 b.txt 当中
  14. fs.write(wfd, buf, 0, 10, 0, (err, written) => {
  15. console.log('写入成功')
  16. })
  17. })
  18. })
  19. }) */
  20. // 02 数据的完全拷贝
  21. /* fs.open('a.txt', 'r', (err, rfd) => {
  22. fs.open('b.txt', 'a+', (err, wfd) => {
  23. fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
  24. fs.write(wfd, buf, 0, 10, 0, (err, written) => {
  25. fs.read(rfd, buf, 0, 5, 10, (err, readBytes) => {
  26. fs.write(wfd, buf, 0, 5, 10, (err, written) => {
  27. console.log('写入成功')
  28. })
  29. })
  30. })
  31. })
  32. })
  33. }) */
  34. const BUFFER_SIZE = buf.length
  35. let readOffset = 0
  36. fs.open('a.txt', 'r', (err, rfd) => {
  37. fs.open('b.txt', 'w', (err, wfd) => {
  38. function next () {
  39. fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => {
  40. if (!readBytes) {
  41. // 如果条件成立,说明内容已经读取完毕
  42. fs.close(rfd, ()=> {})
  43. fs.close(wfd, ()=> {})
  44. console.log('拷贝完成')
  45. return
  46. }
  47. readOffset += readBytes
  48. fs.write(wfd, buf, 0, readBytes, (err, written) => {
  49. next()
  50. })
  51. })
  52. }
  53. next()
  54. })
  55. })

常见目录操作 API

  • access: 判断文件或目录是否具有操作权限
  • stat:获取目录文件信息
  • mkdir: 创建目录
  • rmdir: 删除目录
  • readdir: 读取目录中内容
  • unlink: 删除指定文件 ```typescript const fs = require(‘fs’)

// 一、access / fs.access(‘a.txt’, (err) => { if (err) { console.log(err) } else { console.log(‘有操作权限’) } }) /

// 二、stat / fs.stat(‘a.txt’, (err, statObj) => { console.log(statObj.size) console.log(statObj.isFile()) console.log(statObj.isDirectory()) }) /

// 三、mkdir / fs.mkdir(‘a/b/c’, {recursive: true}, (err) => { if (!err) { console.log(‘创建成功’) }else{ console.log(err) } }) /

// 四、rmdir fs.rmdir(‘a’, {recursive: true}, (err) => { if (!err) { console.log(‘删除成功’) } else { console.log(err) } })

// 五、readdir / fs.readdir(‘a/b’, (err, files) => { console.log(files) }) /

// 六、unlink / fs.unlink(‘a/a.txt’, (err) => { if (!err) { console.log(‘删除成功’) } }) /

  1. <a name="JJvw7"></a>
  2. ### 创建目录之同步实现
  3. ```typescript
  4. const fs = require('fs')
  5. const path = require('path')
  6. /**
  7. * 01 将来调用时需要接收类似于 a/b/c ,这样的路径,它们之间是采用 / 去行连接
  8. * 02 利用 / 分割符将路径进行拆分,将每一项放入一个数组中进行管理 ['a', 'b', 'c']
  9. * 03 对上述的数组进行遍历,我们需要拿到每一项,然后与前一项进行拼接 /
  10. * 04 判断一个当前对拼接之后的路径是否具有可操作的权限,如果有则证明存在,否则的话就需要执行创建
  11. */
  12. function makeDirSync (dirPath) {
  13. let items = dirPath.split(path.sep)
  14. for(let i = 1; i <= items.length; i++) {
  15. let dir = items.slice(0, i).join(path.sep)
  16. try {
  17. fs.accessSync(dir)
  18. } catch (err) {
  19. fs.mkdirSync(dir)
  20. }
  21. }
  22. }
  23. makeDirSync('a\\b\\c')

创建目录之异步实现

  1. const fs = require('fs')
  2. const path = require('path')
  3. const {promisify} = require('util')
  4. /* function mkDir (dirPath, cb) {
  5. let parts = dirPath.split('/')
  6. let index = 1
  7. function next () {
  8. if (index > parts.length) return cb && cb()
  9. let current = parts.slice(0, index++).join('/')
  10. fs.access(current, (err) => {
  11. if (err) {
  12. fs.mkdir(current, next)
  13. }else{
  14. next()
  15. }
  16. })
  17. }
  18. next()
  19. }
  20. mkDir('a/b/c', () => {
  21. console.log('创建成功')
  22. }) */
  23. // 将 access 与 mkdir 处理成 async... 风格
  24. const access = promisify(fs.access)
  25. const mkdir = promisify(fs.mkdir)
  26. async function myMkdir (dirPath, cb) {
  27. let parts = dirPath.split('/')
  28. for(let index = 1; index <= parts.length; index++) {
  29. let current = parts.slice(0, index).join('/')
  30. try {
  31. await access(current)
  32. } catch (err) {
  33. await mkdir(current)
  34. }
  35. }
  36. cb && cb()
  37. }
  38. myMkdir('a/b/c', () => {
  39. console.log('创建成功')
  40. })

递归删除目录

  1. const { dir } = require('console')
  2. const fs = require('fs')
  3. const path = require('path')
  4. /**
  5. * 需求:自定义一个函数,接收一个路径,然后执行删除
  6. * 01 判断当前传入的路径是否为一个文件,直接删除当前文件即可
  7. * 02 如果当前传入的是一个目录,我们需要继续读取目录中的内容,然后再执行删除操作
  8. * 03 将删除行为定义成一个函数,然后通过递归的方式进行复用
  9. * 04 将当前的名称拼接成在删除时可使用的路径
  10. */
  11. function myRmdir (dirPath, cb) {
  12. // 判断当前 dirPath 的类型
  13. fs.stat(dirPath, (err, statObj) => {
  14. if (statObj.isDirectory()) {
  15. // 目录---> 继续读取
  16. fs.readdir(dirPath, (err, files) => {
  17. let dirs = files.map(item => {
  18. return path.join(dirPath, item)
  19. })
  20. let index = 0
  21. function next () {
  22. if (index == dirs.length) return fs.rmdir(dirPath, cb)
  23. let current = dirs[index++]
  24. myRmdir(current, next)
  25. }
  26. next()
  27. })
  28. } else {
  29. // 文件---> 直接删除
  30. fs.unlink(dirPath, cb)
  31. }
  32. })
  33. }
  34. myRmdir('tmp', () => {
  35. console.log('删除成功了')
  36. })

模块化历程

  • 传统开发常见问题
    • 命名冲突和污染
    • 代码冗余,无效请求多
    • 文件间的依赖关系复杂
  • 模块就是小儿精且利用维护的代码片段
  • 利用函数、对象、自执行函数实现分块

    模块化规范

  • 模块化是前端走向工程化中的重要的一环

  • 早期 JavaScript 语言层面没有模块化规范
  • Commonjs、AMD、CMD、都是模块化规范
  • ES6 中将模块化纳入标准规范
  • 当下常用规范是 Commonjs 与 ESM

    Commonjs 规范

  • 模块引用

  • 模块定义
  • 模块标识

    module 属性

  • 任意 js 文件就是一个模块,可以直接使用 module 属性

  • id: 返回模块标识符,一般是一个绝对路径
  • filename: 返回文件模块的绝对路径
  • loaded: 返回布尔值,表示模块是否完成加载
  • parent:返回对象存放调用当前模块的模块
  • children: 返回数组,存放当前模块调用的其他模块
  • exports:返回当前模块需要暴露的内容
  • paths:返回数组,存放不同目录下的 node_modules 位置

    modules.exports 与 exports 有何区别?

  • CommonJS中规定 通过 modules.exports 进行导出操作

  • exports 是NodeJS 为了方便操作,给每个文件提供了一个变量,指向了 modules.exports 所对应的内存地址,因此可以通过 exports 导出相应的内容
  • exports 导出的时候不能赋值, 因为会切断和 modules.exports对应的联系,成为一个局部变量,无法和外面联系

require 属性

  • 基本功能是读入并且执行一个模块文件
  • resolve: 返回模块文件绝对路径
  • extensions: 依据不同后缀名执行解析操作
  • main:返回主模块对象

CommonJS 规范

  • CommonJS 规范起初是为了弥补 JS 语言模块化缺陷
  • CommonJS 是语言层面的规范,当前主要用于 Node.js
  • CommonJS 规定模块化分为引入、定义、标识符三个部分
  • Modules 在人意模块中可直接使用包含模块信息
  • Require 接收标识符,加载目标模块
  • Exports 与 module.exports 都能导出模块数据
  • CommonJS 规范定义模块的加载是同步完成

    Nodejs 与 CommonJS

  • 任意一个文件就是一模块,具有独立作用域

  • 使用 module.exports与 require 实现模块的导入导出
  • module 属性及其常见信息获取
  • exports 导出数据及其与 module.exports 区别
  • CommonJS 规范下的模块同步加载
  1. // 一、导入
  2. /* let obj = require('./m')
  3. console.log(obj) */
  4. // 二、module
  5. // let obj = require('./m')
  6. // 三、exports
  7. /* let obj = require('./m')
  8. console.log(obj) */
  9. // 四、同步加载
  10. /* let obj = require('./m')
  11. console.log('01.js代码执行了') */
  12. let obj = require('./m')
  13. console.log(require.main == module)
  14. // m.js文件
  15. // 一、模块的导入与导出
  16. /* const age = 18
  17. const addFn = (x, y) => {
  18. return x + y
  19. }
  20. module.exports = {
  21. age: age,
  22. addFn: addFn
  23. } */
  24. // 二、module
  25. /* module.exports = 1111
  26. console.log(module) */
  27. // 三、exports
  28. // exports.name = 'zce'
  29. /* exports = {
  30. name: 'syy',
  31. age: 18
  32. } */
  33. // 四、同步加载
  34. /* let name = 'lg'
  35. let iTime = new Date()
  36. while(new Date() -iTime < 4000) {}
  37. module.exports = name
  38. console.log('m.js被加载导入了') */
  39. /* console.log(require.main == module) */
  40. module.exports = 'lg'

模块加载及加载流程

  • 模块分类
    • 内置模块
    • 文件模块
  • 模块加载速度
    • 核心模块: Node 源码编译时写入到二进制文件中
    • 文件模块: 代码运行时,动态加载
  • 加载流程

    • 路径分析: 依据标识符确定模块位置
      • 路径标识符
      • 非路径标识符: 常见于核心模块
    • 文件定位: 确定目标模块中具体的文件及文件类型
      • 项目下存在m1.js模块,导入时使用require(‘m1’)语法
      • m1.js -> m1.json -> m1.node
      • 查找package.json 文件,使用 JSON.parse() 解析
      • main.js -> main.json -> main.node
      • 将 index 做为目标模块中的具体文件名称
    • 编译执行: 采用对应的方式完成文件的编译执行
      • 将某个具体类型的文件按照相应的方式及进行编译执行
      • 创建新对象,按路径载入,完成编译执行
      • JS 文件的编译执行
        • 使用 fs 模块同步读入目标文件内容
        • 对内容进行语法包装,生成可执行 JS 函数
        • 调用函数时传入exports、module、require等属性值
      • JSON 文件编译执行
        • 将读取到的内容通过 JSON.parse() 进行解析
    • 缓存优先原则
      • 提高模块加载速度
      • 当前模块不存在,则经历一次完成加载流程
      • 模块加载完成后,使用路径作为索引进行缓存

        核心模块之 Events

        events 与 EventEmitter

  • node.js 是基于事件驱动的一步操作架构,内置 events 模块

  • events 模块提供了 EventEmitter 类
  • node.js 中很多内置核心模块集成 EventEmitter

EventEmitter 常见API

  • on: 添加当事件被触发时调用的回调函数
  • emit: 触发事件,按照注册时的序同步调用每个事件监听器
  • once:添加当事件在注册之后首次被触发时调用的回调函数
  • off: 移除特定的监听器

发布订阅模式

  • image.png
  • 发布订阅要素
    • 缓存队列,存放订阅者信息
    • 具有增加、删除订阅的能力
    • 状态改变时通知所有订阅者执行监听
  • 发布订阅中存在调度中心
  • 状态发生改变时,发布订阅无须主动通知 ```typescript class PubSub{ constructor() { this._events = {} }

    // 注册 subscribe(event, callback) { if (this._events[event]) {

    1. // 如果当前 event 存在,所以我们只需要往后添加当前次监听操作
    2. this._events[event].push(callback)

    } else {

    1. // 之前没有订阅过此事件
    2. this._events[event] = [callback]

    } }

    // 发布 publish(event, …args) { const items = this._events[event] if (items && items.length) {

    1. items.forEach(function (callback) {
    2. callback.call(this, ...args)
    3. })

    } } }

let ps = new PubSub() ps.subscribe(‘事件1’, () => { console.log(‘事件1执行了’) }) ps.subscribe(‘事件1’, () => { console.log(‘事件1执行了—-2’) })

ps.publish(‘事件1’) ps.publish(‘事件1’) ```

EventEmitter 类源码实现