Nodejs核心模块(一)

一、内置模块path

const path = require('path');

// 1、获取路径中的基础名称 basename: 第一个参数是接受路径 第二个参数是后缀如果有后缀返回后缀前的文件名称
// 返回路径中的最后一个部分不管事文件还是目录, 如果最后有目录分割符会直接忽略 path.basename()
console.log(__filename)
console.log(path.basename(__filename, '.css'))
console.log(path.basename('/a/b/c'))

// 2、获取路径目录名 path.dirname()
/** 
 * 01、返回最后一个部分的上一级目录所在路径
 * 02、如果末尾出现路径分隔符不会被处理
 */
console.log(path.dirname(__filename))
console.log(path.dirname('/a/b/c'))
console.log(path.dirname('/a/b/c/'))

// 3、获取路径的扩展名 path.extname()
/** 
 * 01、返回path路径中响应文件的后缀名
 * 02、如果path路径中存在多个. 他匹配的是最后一个.到结尾的内容
 */
console.log(path.extname(__filename))
console.log(path.extname('/a/b/c'))
console.log(path.extname('/a/b/index.html.js.css'))
console.log(path.extname('/a/b/index.html.js.'))

// 4、解析路径
/**
 *  01、path.parse 接受一个路径,返回一个对象对象中包含着路径信息 root dir base ext name
 *  02、 会忽略结尾的路径分隔符
 *  03、 如果是相对路径root是空 如果是绝对路径 root是根/
 */
const obj = path.parse('./a/b/c')
console.log(obj)

// 5、序列化路径
/**
 * 与解析路径相反
 */
const obj = path.parse('./a/b/c')
console.log(obj)
console.log(path.format(obj))

// 6、判断路径是否是绝对路径
console.log(path.isAbsolute('foo')) // false
console.log(path.isAbsolute('/foo')) // true
console.log(path.isAbsolute('///foo')) //true
console.log(path.isAbsolute(''))  //false
console.log(path.isAbsolute('.')) // false
console.log(path.isAbsolute('./bar')) //false

// 7、拼接路径
/** 
 *  01、 拼接给定的路径片段生成完整路径
 *  02、 可以识别../ ./ 等操作
 *  03、 忽略为空的路径片段 如果只有空路径 那么返回一个. 代表当前目录
 */
console.log(path.join('a/b', 'c', 'index.html')) // a\b\c\index.html
console.log(path.join('/a/b', 'c', 'index.html')) // \a\b\c\index.html
console.log(path.join('/a/b', 'c', '../' ,'index.html')) // \a\b\index.html
console.log(path.join('/a/b', 'c', './' ,'index.html')) // \a\b\c\index.html
console.log(path.join('/a/b', 'c', '' ,'index.html')) // \a\b\c\index.html
console.log(path.join('')) // .

// 8、规范化路径
console.log(path.normalize('/a/b/c/d')) // \a\b\c\d
console.log(path.normalize('/a///b/c../d')) // \a\b\c..\d
console.log(path.normalize('/a/\\b/c\\/d')) // \a\b\c\d
console.log(path.normalize('/a//\b/c\\/d'))  // \a\c\d

// 9、返回绝对路径
/**
 * resolve([form], to) from和to是一个相对的关系 取决于第二个参数是怎么传的
 * 如果传参中没有绝对路径那么返回的前缀是cwd
 */
console.log(path.resolve()) // D:\项目\node学习
console.log(path.resolve('a','b')) // D:\项目\node学习\a\b
console.log(path.resolve('a','/b')) // D:\b
console.log(path.resolve('/a','b')) // D:\a\b
console.log(path.resolve('/a','/b')) // D:\b
console.log(path.resolve('index.html')) // D:\项目\node学习\index.html

二、全局变量buffer

Buffer让JavaScript可以操作二进制

IO行为操作的就是二进制

数据的端到端传输会有生产者和消费者,生产和消费往往存在等待,产生等待时数据就存放在Buffer缓冲区,Buffer一般配合Stream流使用 充当数据缓冲区

Nodejs中Buffer是一片内存空间,不占据V8的堆内存大小;内存的使用由Node来控制,由V8的GC回收

创建Buffer:

/**
 *  创建buffer方法
 *  alloc: 创建指定字节大小的buffer
 *  allocUnsafe: 创建指定大小的buffer(不安全) 有空闲内存就会拿来用
 *  from:接收数据,创建数据对应buffer
 */
const b1 = Buffer.alloc(10);
console.log(b1) // <Buffer 00 00 00 00 00 00 00 00 00 00>
const b2 = Buffer.allocUnsafe(10);
console.log(b2) // <Buffer 00 00 00 00 00 00 00 00 00 00>
const b3 = Buffer.from('中') // 第一个参数是传入的数据可传入字符串数组和本身是buffer的类型数据 第二个参数是编码方式默认utf-8
console.log(b3) // <Buffer e4 b8 ad>
const b4 = Buffer.from([1,2,3])
console.log(b4) // // <Buffer 01 02 03>
console.log(b3.toString()) // 中
const b5 = Buffer.from([0xe4, 0xb8, 0xad], 'utf-8')
console.log(b5) // <Buffer e4 b8 ad>
console.log(b5.toString()) // 中
// 利用老的buffer空间创建新的buffer空间,其实是根据老空间的大小长度数据进行的拷贝,修改老空间数据不会影响新空间数据
const b6 = Buffer.alloc(3)
const b7 = Buffer.from(b6)
console.log(b6, b7) // <Buffer 00 00 00> <Buffer 00 00 00>
b6[0] = 1
console.log(b6, b7,11) // <Buffer 01 00 00> <Buffer 00 00 00> 11

为什么 allocUnsafe 不安全?

关键在于给 Buffer 申请分配的内存是否被初始化。被初始化的内存即填充了默认的数据,例如 0。没被初始化的内存可能包含敏感的旧数据,这是不安全的。

Buffer.alloc() 分配的内存是初始化过的内存(被 0 填满覆写),这种创建方式虽然慢但被认为是安全的。

Buffer.allocUnsafe() 分配的内存没有被初始化,所以分配速度相当快,但内存中可能存在敏感旧数据,在 Buffer 可读的情况下,可能会泄露数据,这种方式被认为是不安全的。一般会建议手动通过 buf.fill(0) 初始化或写满这个 Buffer。

虽然在使用 Buffer.allocUnsafe() 时有明显的性能优势,但必须额外小心,以避免给应用程序引入安全漏洞。

为什么不使用 new 创建 Buffer?

在 Nodejs 的 v6 版本之前可以直接通过 new 实例化 Buffer 对象。

但是这种方式提供给 Buffer 实例对象的操作权限实在是太大了,所以在后续的版本中对它进行了一些处理。

主要还是使用这种方式分配的内存是没有初始化过的,包含敏感数据,Node.js 认为在分配内存的安全性上需要更加明确的区分,所以建议使用 Buffer 类的静态方法创建,而不是通过 new 实例化。

Buffer的实例方法

/**  Buffer实例方法
 *  fill: 使用数据填充buffer
 *  write: 向buffer中写入数据
 *  toString: 从buffer中提取数据
 *  slice: 截取buffer
 *  indexOf: 在buffer中查找数据
 *  copy: 拷贝buffer中的数据
 */
// fill
/**
 * 第一个参数是要填充的数据如果长度比buffer长度小会被反复填充满为止,如果比buffer长度长那么就填充到buffer的长度
 * 第二个参数是从buffer的哪个下标开始填充
 * 第三个参数是填充到哪里 经典顾头不顾腚
 */
let buf = Buffer.alloc(6)
buf.fill(123)
console.log(buf, buf.toString())

//write
/**
 * 第一个参数要写入的数据,他不会和fill一样填充
 * 第二个参数是从哪里开始写
 * 第三个参数是写入的长度
 */
buf.write('123',1,4)
console.log(buf, buf.toString())

// toSting:根据编码转换buffer数据
/**
 * 第一个参数要转换成的编码格式
 * 第二个参数截取数据的起始位置
 * 第三个参数截取数据的结束为止 顾头不顾腚
 */
buf = Buffer.from('啊哈哈)
console.log(buf,buf.toString('utf-8', 3, 6))

//slice:像操作数组一样对buffer进行截取,然后返回
/**
 * 第一个参数,第二个参数截取的开始和结束 顾头不顾尾
 * 如果想从后往前就传入负数
 */
buf = Buffer.from('啊哈哈')
const b1 = buf.slice(-3)
console.log(b1,b1.toString())

// indexOf
/**
 * 传入目标字符 找到第一次出现的下标,如果没有是-1
 */
buf = Buffer.from('云顶,铲铲,云铲铲')
console.log(buf.indexOf('哈'))

// copy
/**
 * b2是数据源 b1是拷贝的容器
 * 第二个参数是 从容器的哪个位置去进行写入操作
 * 第三个参数 从原buffer的哪个位置进行的读取操作
 * 第四个参数是从原buffer的哪个位置结束 顾头不顾腚
 */
const b1 = Buffer.alloc(6)
const b2 = Buffer.from('啊哈')
b2.copy(b1, 3, 3, 6)
console.log(b1.toString(), b2.toString())

自定义Buffer方法之split

Buffer.prototype.split = function (sep) {
  let len = Buffer.from(sep).length // 获取分割符字节长度
  let ret = [] // 返回的分割后的数组
  let start = 0 // 分割起始位置
  let offset = 0 // 偏移量
  while ( (offset = this.indexOf(sep, start)) !== -1) {
    ret.push(this.slice(start, offset))
    start = offset + len
  }
  // 追加尾部
  ret.push(this.slice(start))
  return ret
}

const buf = Buffer.from('怎么说呢,什么情况,怎么会这样')
console.log(buf.split(',').map(v => v.toString()))

三、FS模块

fs是内置核心模块,提供文件系统操作的API

fs模块结构:

        1、fs基本操作类:实现文件信息的获取,如判断当前是目录还是文件、文件的可读流和可写流的操作、文件的监听行为等

        2、fs常用API:如文件的打开/关闭、文件的增删改查等

关于系统和文件的前置知识

权限位

这里的“权限”指在当前操作系统内,不同的用户角色对于当前文件所具备的不同权限操作。

文件的权限操作分为三种:

  • r:read 读权限,读取/查看
  • w:write 写权限,修改文件
  • x:execute 执行权限,执行文件
  • -:无权限

以上权限都不能指定文件的删除权限,删除权限由是否拥有对该文件所在目录的写权限决定。 

用八进制数字进行表示:

  • r:4
  • w:2
  • x:1
  • 无权限:0

例如:rw 表示拥有读写权限,数字表示就是 64+2

操作系统中将用户分为三类:

文件的所有者:一般指的是当前用户
文件的所属组:当前用户所在组
其它用户:例如访客用户
文件的权限由以上三类用户依次组成,每类用户的权限由 r、w、x 依次组成,或用一个八进制数字表示。

例如:

774或rwxrwxr--:当前用户和当前用户所属组拥有最大权限,其它用户只拥有读权限
777或rwxrwxrwx 表示为任意用户分配对当前文件的最大权限。

windows 系统下,文件的权限一般是可读可写但不可执行,即 rw-rw-rw-666)。 

文件标志符
文件系统标志符 flag 表示文件打开的方式。

常见标识符有很多,这里只列举一部分:

a:打开文件进行追加,如果文件不存在,则创建文件
r:打开文件进行读取,如果文件不存在,则抛出异常
w:打开文件进行写入,如果文件不存在,则创建
s:表示以同步模式操作,配合 a、r、w 进行使用
x:表示排它操作,如果路径存在则失败,配合 a、w 使用
+:表示附加操作,配合 a、w、r 使用,a和w包含写入操作,所以 a+和 w+附加 r 读取操作,而 r+ 附加 w 写入操作。
r+ 和 w+ 的区别是:

如果文件不存在,前者抛出异常,后者会创建文件。
w+ 会将文件内容清空,再写入;r+ 会读取文件内容,从开头开始覆盖每个字节的内容,不会清空

文件描述符
文件描述符 fd 就是操作系统分配给被打开文件的数字标识。

这个标识用于识别和跟踪每个特定文件。

windows 系统采用了不同但概念类似于文件描述符的机制追踪资源,为了方便用户,Nodejs 抽象了操作系统之间的差异,并为所有打开的文件分类的一个数字文件描述符。

在 Nodejs 中每操作一个文件,文件描述符就会递增一次,并且这个描述符一般是从 3 开始,因为 0、1、2 被标准输入、标准输出、标准错误占用了。

程序首次使用 fs.open() 打开一个文件的时候会获得一个 fd,它就是这个文件的描述符,并且从 3 开始。

fs 总结
        fs 是 Nodejs 中内置的核心模块,所有与文件相关的操作都要通过它的 API 完成
        代码层面上 fs 分为基本操作类和常用 API
        文件操作有三个常用概念:权限位、文件标志符、文件描述符

文件读写与拷贝操作

文件操作API

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

        writeFile: 向指定文件中写入数据

        appendFile:追加的方式向指定文件中写入数据

        copyFile:将某个文件中的数据拷贝至另一文件

        watchFile:对指定文件进行监控

readFilewriteFileappendFilecopyFile 都是一次性的操作,例如 copyFile 会将文件内容一次性获取并放到内存中,然后再一次性写入另一个文件。这些都不适用于大内存的文件操作。

Nodejs 中几乎所有文件 API 操作都有同步和异步两种方式,同步 API 名称比异步 API 名称多个 Sync,如 readFile 对应的同步 API 是 readFileSync,更多可以查看 Nodejs 文档

const fs = require('fs')
const path = require('path')

// readFile
/**
 * Nodejs回调函数为错误优先,即第一个参数是错误信息,后面才是数据
 * readFile第一个参数为文件路径建议绝对路径,第二个参数是以什么方式编码,第三个为回调函数
 */
fs.readFile(path.resolve('data.txt'), 'utf-8' , (err, data) => {
  console.log(err)
  console.log(data)
})

// writeFile
/**
 * writeFile第一个参数为文件路径,第二个参数是需要写入的内容,第三个是一个对象 mode是权限位 flag是文档标记符 encoding是编码方式
 * writeFile默认的是覆盖写如操作会覆盖原文档的内容
 * 如果写入的路径不存在,那么他会创建这个文件
 */
fs.writeFile(path.resolve('data.txt'), '啊', {
  mode: 0o666, // 八进制0o666 十进制438
  flag: 'r+', // 默认w
  encoding: 'utf8'
}, (err) => {
  if (!err) {
    fs.readFile('data.txt','utf-8',(err, data) => {
      console.log(err)
      console.log(data)
    })
  }
})

// appendFile
/**
 * 第一个参数文件路径,第二个参数写入内容
 */
fs.appendFile(path.resolve('data.txt'), '今天整一个', (err) => {
  console.log('写入成功')
})

// copyFile
/**
 * 第一个参数源文件,第二个参数目标文件
 */
fs.copyFile('data.txt', 'test.txt', (err) => {
  console.log('拷贝成功')
})

// watchFile
/**
 * 监听文件变化
 * 第一个参数是文件路径,第二个参数的interval属性代表多长时间监听一次
 * 回调函数中 curr是监听修改后的文件对象, prev是修改前的文件对象(mTime是修改时间)
 */
// unwatchFile 结束监听
fs.watchFile('data.txt', { interval:20 }, (curr,prev) => {
  // 判断是否已经进行过修改
  if (curr.mtime !== prev.mtime) {
    console.log('文件被修改了')
    fs.unwatchFile('data.txt')
  }
})

文件操作实现md转html

需要依赖

marked:把md文件转换成html的格式

browserSync:开启一个web服务,打开html文件并实时更新

const fs = require('fs')
const path = require('path')
const marked = require('marked')
const browserSync = require('browser-sync')

// 01 读取 markdown 和 css 的内容
// 02 将上述读取的内容,替换模板中的占位符,生成最终需要展示的 html 字符串
// 03 将最终 html 字符串写入到指定的 html 文件中
// 04 监听 markdown 文件内容的变化,实时更新 html 内容
// 05 实时显示 html 内容

// html 模板
const temp = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    {{style}}
  </style>
</head>
<body>{{content}}</body>
</html>
`

// markdown 文件路径
// 启动的时候 node index.js md文件名  所以md文件的路径就是目录名dirname加启动参数的第三个
const mdPath = path.join(__dirname, process.argv[2])
// css 文件路径
const cssPath = path.resolve('github.css')
// html 最终转化的文件路径
// 指定为与 markdown 文件同目录下的同名 html 文件
const htmlPath = mdPath.replace(path.extname(mdPath), '.html')

// 监听 markdown 文件
fs.watchFile(mdPath, (current, previous) => {
  if (current.mtime !== previous.mtime) {
    // 读取 markdown 内容
    fs.readFile(mdPath, 'utf8', (err, data) => {
      // 将 markdown 转化为 html
      const content = marked(data)
      // 读取 css 内容
      fs.readFile(cssPath, 'utf8', (err, style) => {
        // 替换内容
        const html = temp.replace('{{style}}', style).replace('{{content}}', content)

        // 写入指定 html 文件
        fs.writeFile(htmlPath, html, err => {
          console.log('写入成功')
        })
      })
    })
  }
})

// 开启服务 显示 html 内容
browserSync.init({
  server: {
    baseDir: __dirname, // 服务的根目录
    index: path.basename(htmlPath) // 指定首页的文件名
  },
  watch: true // 监听更新
})

 文件的打开与关闭

前面的 API 是将文件中的数据一次性的读取/写入到内存中,这种方式对于大体积的文件来说,显然不合理。

所以需要实现一个可以边读边写或边写边读的操作方式,这就需要将文件的打开、读取、写入、关闭看作各自独立的环节。

const fs = require('fs');
const path = require('path');
// open
/**
 * 第一个参数是打开的文件路径
 * 第二个参数是文件标志符,进行什么样的操作,
 * 回调中的第二个参数是文件描述符,用于追踪文件从3开始 0 1 2被标准输入、输出、错误占用
 */
fs.open(path.resolve('data.txt'), 'r', (err, fd) => {
  console.log(fd)
})
// close
fs.open(path.resolve('data.txt'), 'r', (err, fd) => {
  console.log(fd)
  // 第一个参数传入打开文件的文件描述符追踪关闭哪一个文件
  fs.close(fd, (err) => {
    console.log('关闭成功')
  })
})

大文件读写操作

 

A文件中的数据要想拷贝到B文件中,默认情况下需要内存作为中转。

如果是一次性的操作,就会存在内存占满并且溢出的潜在问题。

因此我们更期望有一个中间暂存区,一点一点的读取,然后一点一点的写入。

而这个中间暂存区就是 Buffer。

const fs = require('fs');
const path = require('path');

// read:所谓的读操作就是将文件从磁盘中读取然后写入到buffer中

let buf = Buffer.alloc(10)
/**
 * 第一个参数是fd 也就是文件标识符,标记是读取哪一个文件
 * 第二个参数是缓冲区,指明写入哪一个缓冲区
 * 第三个参数是偏移量,表示从buffer的哪一个位置开始执行写入
 * 第四个参数是length,表示当前次写入的长度
 * 第五个参数是position,表示从文件的哪一个位置开始进行读取操作
 * 缓冲区的大小必须大于文件内容大小不然会报错
 */
fs.open(path.resolve('data.txt'), 'r', (err, rfd) => {
  fs.read(rfd, buf, 0 ,4, 3, (err,readBytes,data) => {
    console.log(readBytes,data,data.toString())
    fs.close(rfd)
  })
})
// write:将缓冲区里的内容写入到磁盘文件中,所谓的写也就是读
/**
 * 第一个参数fd,标记写入哪一个文件
 * 第二个参数缓冲区 指明写入哪一个缓冲区
 *  第三个参数是偏移量,表示从buffer的哪一个位置开始执行读取
 * 第四个参数是length,表示当前次读取的长度
 * 第五个参数是position,表示从文件的哪一个位置开始进行读取写入 一般都是0不动
 */
buf = Buffer.from('1234567890')
fs.open(path.resolve('b.txt'), 'w', (err,wfd) => {
  fs.write(wfd, buf, 0, 4, 0, (err,writeBytes,buffer) => {
    // buffer指向写入的数据源
    console.log(writeBytes)
    console.log(buffer)
    console.log(buffer.toString)
    fs.close(wfd) // 用完之后关掉 不然每open一次文件标识符都会+1 会造成资源浪费
  })
})

文件拷贝自定义实现

fs的copyFile是针对readFile和writeFile进行实现的,他们都是对文件的一次性操作,对大体积文件来说非常不合适,

基于文件读写对大体积文件进行拷贝api的实现

// 将A文件拷贝到B文件 用read读数据存入buffer 从buffer中取数据写入B文件
const fs = require('fs')
const buf = Buffer.alloc(10)

// 数据完全拷贝
const BUFFER_SIZE = buf.length // 每次读取数据的字节数
let readOffset = 0
fs.open('copyA.txt', 'r', ( err, rfd ) => {
  fs.open('copyB.txt', 'w', ( err, wfd ) => {
    function next() {
      // position指定为null 自动更新读取文件的起始位置
      fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes, buffer) => {
        if (!readBytes) {
          fs.close(rfd);
          fs.close(wfd);
          console.log('拷贝完成')
          return
        }
        readOffset += BUFFER_SIZE
        fs.write(wfd, buf, 0, readBytes, (err, writeBytes, buffer) => {
          next()
        })
      });
    }
    next()
  })
})

对于大文件的拷贝,在开发中最优的实现方式是流操作

 目录操作API

access:判断用户是否具有当前文件或目录的操作权限
stat:获取目录及文件信息
mkdir:创建目录 make directory
rmdir:删除目录 remove directory
readdir:读取目录中的内容
unlink:删除文件
rm:删除文件和目录 新增于 v14.14.0,rmdir 递归删除的替代推荐 ;Nodejs v14.14.0 推荐使用 fs.rm 代替 fs.rmdir 的 recursive 选项;Nodejs v16.0.0 弃用 fs.rmdir 的 recursive 选项,使用将导致错误
 

const fs = require('fs');
// access 判断有无操作权限,用于判断目录是否存在
fs.access('data.txt', err => {
  if (err) {
    console.log(err);
  } else {
    console.log('有操作权限')
  }
})

// stat
/**
 * 回调函数第二个参数是文件的信息
 * size:内容字节数
 * isFile:判断是否是文件
 * isDirectory:判断是否是目录文件夹
 */
fs.stat('data.txt', (err,statObj) => {
  console.log(statObj.size)
  console.log(statObj.isFile())
  console.log(statObj.isDirectory())
})

// mkdir
/**
 * 默认情况下创建的是路径最后部分,创建前提是保证父级目录全部存在
 * 下面是创建c如果a,b文件夹妹有那么会创建失败
 * 第二个参数如果传入 recursive是true那么他就会递归创建,传入的路径会依次创建
 */
fs.mkdir('a/b/c', err => {
  if(err){
    console.log(err)
  } else {
    console.log('创建成功')
  }
})
fs.mkdir('a/b/c', { recursive: true}, err => {
  if(err){
    console.log(err)
  } else {
    console.log('创建成功')
  }
})

// rmdir
/**
 * 默认情况下删除的是路径最后的部分
 * 如果删除的不是目录或者路径不存在就会报错
 * 如果要删除的目录不是非空目录会报错
 * 同mkdir一样 第二个参数传入recursive 可以递归删除用于删除非空的目录
 * 如果递归删除目录下有文件类型文件也会删除掉
 */
fs.rmdir('a', err => {
  if (err) {
    console.log(err)
  } else {
    console.log('删除成功')
  }
})
fs.rmdir('a', { recursive: true}, err => {
  if (err) {
    console.log( err )
  } else {
    console.log('删除成功')
  }
})

// readdir
/**
 * 仅读取当前目录的下一层文件列表,不会递归读取
 * 回调中的files是文件名组成的数组
 */
fs.readdir('a', (err, files) => {
  console.log(files)
})

// unlink 删除文件
/**
 * 删除文件类型的 如果是目录会报错
 * 删除的是路径中最后的部分,如果文件不存在就会报错
 */
fs.unlink('a', err => {
  if (err) {
    console.log(err)
  } else {
    console.log('文件删除成功')
  }
})

同步模拟递归创建目录

const fs = require('fs');
const path = require('path');

const makeDir = (dirPath) => {
  const items = dirPath.split(path.sep); // 获取当前平台路径分隔符
  for (let i = 1; i <= items.length; i++) {
    const dir = items.slice(0, i).join(path.sep);
    try {
      // 判断文件的可操作权限 (文件是否存在)
      fs.accessSync(dir)
    } catch (e) {
      fs.mkdirSync(dir)
    }
  }
}

makeDir(path.resolve('a/b/c'))

创建目录异步实现

const makeDir = (dirPath, cb) => {
    const items = dirPath.split(path.sep); // 获取当前平台路径分隔符
    let index = 1
    const next = () => {
      if (index > items.length) return cb && cb()
      const dir = items.slice(0, index++).join(path.sep)
      fs.access(dir, err => {
        if (err) {
          fs.mkdir(dir, err => {
            if (err) {
              console.log(err)
            } else {
              next()
            }
          })
        } else {
          next()
        }
      })
    }
    next()
}
makeDir(path.resolve('a/b/c'), () => {
  console.log('创建成功')
})

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值