前端培训-中级阶段(47)- Node.js 10.x 文件模块


思否
前端培训 Node.js JavaScript

转载自 SegmentFault

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。

使用 Node.js 很大一部分的使用场景是,写个小脚本批量处理一下重复无聊的任务。比如说:

  • 获取所有 json 内容,然后过滤出想要的数据。
  • 把所有图片压缩一下,然后放入压缩包中。
  • 把字体文件提取压缩一下。
  • 把所有文件重命名一下。

这些任务中我们都需要访问文件系统,之前在浏览器环境中其实是没有文件系统概念的,今天我们来学一哈。

Node.js 文件模块

我们可以使用 fs 来操作文件系统。

const fs = require('fs');

回调方式

所有的文件系统操作都具有同步的(10.x 有,历史也有)、回调的(10.x 有,历史也有)、以及基于 promise(12.x 开始就有了,历史没有) 的形式。

同步

同步的形式会阻止 Node.js 事件循环和进一步的 JavaScript 执行,直到操作完成。 异常会被立即地抛出,可以使用 try…catch 处理,也可以冒泡。

const fs = require('fs');

try {
  fs.unlinkSync('文件');
  console.log('已成功地删除文件');
} catch (err) {
  // 处理错误
}

异步

异步的形式总是把完成回调作为其最后一个参数。 传给完成回调的参数取决于具体方法,但第一个参数总是预留给异常。 如果操作被成功地完成,则第一个参数会为 nullundefined

const fs = require('fs');

fs.unlink('文件', (err) => {
  if (err) throw err;
  console.log('已成功地删除文件');
});

promise

基于 Promise 的操作会返回 Promise(当异步操作完成时会被解决)。

const fs = require('fs/promises');

(async function(path) {
  try {
    await fs.unlink(path);
    console.log(`已成功地删除文件 ${path}`);
  } catch (error) {
    console.error('出错:', error.message);
  }
})('文件');

文件路径

支持 字符串(常用Path库就对了)、Buffer(为了兼容)、URL(需要 new URL('file:///C:/') 为入参。可以用相对路径 (process.cwd() 可以查看当前路径),绝对路径。

字符串形式

在 Windows 上,Node.js 遵循独立驱动器工作目录的概念。 当使用没有反斜杠的驱动器路径时,可以观察到此行为。 例如, fs.readdirSync('C:\\') 可能会返回与 fs.readdirSync('C:') 不同的结果。 详见此 MSDN 页面。

一般我们使用 Path 库:

  • 可以直接看一下最终地址
  • 可以正确使用当前系统的路径分隔符,Unix系统是”/“,Windows系统是""。

URL 形式

// 在 Windows 上:

// - 带有主机名的 WHATWG 文件的 URL 会转换为 UNC 路径。
// file://主机名/文件 => 主机名文件
fs.readFileSync(new URL('file://主机名/文件'));

// - 带有驱动器号的 WHATWG 文件的 URL 会转换为绝对路径。
// file:///C:/文件 => C:文件
fs.readFileSync(new URL('file:///C:/文件'));

API

目录 dir 类

fs.readdirSync() 同步获取目录 fs.readdir() 异步获取目录

当调用 fs.readdir()fs.readdirSync()withFileTypes 选项设置为 true)时,则生成的数组会使用 fs.Dirent 对象(而不是字符串或 Buffer)填充。

fs.mkdir(path[, options], callback) 异步地创建目录。回调会传入可能的异常、以及创建的第一个目录的路径(如果 recursivetrue), (err, [path])。可选的 options 参数可以是整数(指定 mode(权限和粘滞位))、或对象(具有 mode 属性和 recursive 属性(指示是否要创建父目录))。 当 path 是已存在的目录时,调用 fs.mkdir() 仅在 recursive 为 false 时才会导致错误。 fs.mkdirSync(path[, options]) 同步地创建目录。 返回 undefined,或创建的第一个目录的路径(如果 recursivetrue)。 这是 fs.mkdir() 的同步版本。

监听 fs.FSWatcher 类

fs.watch() 每当指定监视的文件被修改时,会触发 'change' 事件。

// 使用 fs.watch()监听器的示例。
fs.watch('./tmp', { 
        encoding: 'buffer' 
    }, (eventType, filename) => {
  if (filename) {
    console.log(filename);
  }
});

读取流 fs.ReadStream 类

使用 fs.createReadStream() 函数创建并返回的 fs.ReadStream 实例。

fs.createReadStream(path[, options])

  • path | |

options | flags 参见文件系统 flag 的支持。 默认值: 'r'

  • encoding 默认值: null
  • fd 默认值: null
  • mode 默认值: 0o666
  • autoClose 默认值: true
  • emitClose 默认值: false
  • start
  • end 默认值: Infinity
  • highWaterMark 默认值: 64 * 1024
  • fs | 默认值: null
  • 返回: <fs.ReadStream> 参见可读流。
  • 写入流 fs.WriteStream 类

    使用 fs.createWriteStream() 函数创建并返回的 fs.WriteStream 实例。

    fs.createWriteStream(path[, options])

    • path | |

    options | flags 参见文件系统 flag 的支持。 默认值: 'w'

    • encoding 默认值: 'utf8'
    • fd 默认值: null
    • mode 默认值: 0o666
    • autoClose 默认值: true
    • emitClose 默认值: false
    • start
    • fs | 默认值: null
    • 返回: <fs.WriteStream> 参见可写流。
    • ,.

      文件信息 fs.Stats 类

      fs.Stats 对象提供了关于文件的信息。在遍历的时候我们会判断文件类型,如果是文件就打开,如果是目录就递归

      fs.stat()fs.lstat()fs.fstat()、以及它们的同步方法返回的对象都是此类型。 如果传给这些方法的 options 中的 bigint 为 true,则数值会是 bigint 型而不是 number 型,并且该对象还会包含额外的纳秒级精度的属性(以 Ns 为后缀)。

      • stats.isDirectory() 如果 fs.Stats 对象描述文件系统目录,则返回 true
      • stats.isFile() 如果 fs.Stats 对象描述普通的文件,则返回 true
      • stats.size 文件的大小(以字节为单位)。
      • stats.mtime 表明上次修改此文件的时间戳。 stats.mtimeMs 表明上次修改此文件的时间戳,以 POSIX 纪元以来的毫秒数表示。
      • stats.birthtime 表示此文件的创建时间的时间戳。 stats.birthtimeMs 表明此文件的创建时间的时间戳,以 POSIX 纪元以来的毫秒数表示。

      文件属性的时间值#

      中英对照

      atimeMsmtimeMsctimeMsbirthtimeMs 属性是保存相应时间(以毫秒为单位)的数值。 它们的精度取决于平台。 当将 bigint: true 传给生成该对象的方法时,属性将会是 bigint 型,否则它们将会是数字型。

      atimeNsmtimeNsctimeNsbirthtimeNs 属性是保存相应时间(以纳秒为单位)的 bigint。 仅当将 bigint: true 传给生成该对象的方法时,它们才会出现。 它们的精度取决于平台。

      atimemtimectimebirthtime 是对应时间的 Date 对象。 Date 值和数值没有关联性。 赋值新的数值、或者改变 Date 的值,都将不会影响到对应的属性。

      stat 对象中的时间具有以下语义:

      • atime “访问时间” - 上次访问文件数据的时间。由 mknod(2)utimes(2)read(2) 系统调用更改。
      • mtime “修改时间” - 上次修改文件数据的时间。由 mknod(2)utimes(2)write(2) 系统调用更改。
      • ctime “更改时间” - 上次更改文件状态(修改索引节点数据)的时间。由 chmod(2)chown(2)link(2)mknod(2)rename(2)unlink(2)utimes(2)read(2)write(2) 系统调用更改。
      • birthtime “创建时间” - 创建文件的时间。当创建文件时设置一次。 在不支持创建时间的文件系统上,该字段可能改为保存 ctime1970-01-01T00:00Z(即 Unix 纪元时间戳 0)。 在这种情况下,该值可能大于 atimemtime。 在 Darwin 和其他的 FreeBSD 衍生系统上,也可能使用 utimes(2) 系统调用将 atime 显式地设置为比 birthtime 更早的值。

      在 Node.js 0.12 之前,在 Windows 系统上 ctime 保存 birthtime。 从 0.12 开始, ctime 不再是“创建时间”,而在 Unix 系统上则从来都不是。

      获取权限访问 fs.access、fs.accessSync

      除了判断权限,还可以判断是否存在。不过我们一般可以直接 fs.open() 直接去处理 err 。通常,仅在不直接使用文件时(例如当其可访问性是来自其他进程的信号时),才检查文件的可访问性。

      在 Windows 上,目录上的访问控制策略(ACL)可能会限制对文件或目录的访问。 但是, fs.access() 函数不检查 ACL,因此即使 ACL 限制用户读取或写入,也可能报告路径是可访问的。

      fs.access(path[, mode], callback)

      • path | |
      • mode 默认值: fs.constants.F_OK

      callback err

      测试用户对 path 指定的文件或目录的权限。 mode 参数是一个可选的整数,指定要执行的可访问性检查。 查看文件可访问性的常量了解 mode 的可选值。 可以创建由两个或更多个值按位或组成的掩码(例如 fs.constants.W_OK | fs.constants.R_OK)。

      文件

      • fs.open(path[, flags[, mode]], callback) 异步地打开文件。 fs.openSync(path[, flags, mode]) 同步打开文件
      • fs.read(fd, buffer, offset, length, position, callback)fd 指定的文件中读取数据,写入到 buffer 中。 fs.readSync(fd, buffer, offset, length, position) 同步版本,从指定 fs 中读取数据 fs.readFile(path[, options], callback) 异步地读取文件的全部内容。 fs.readFileSync(path[, options]) 同步版本,读取文件全部内容

      fs.appendFile(path, data[, options], callback) 异步地追加数据到文件,如果文件尚不存在则创建文件。 data 可以是字符串或 Bufferfs.appendFileSync(path, data[, options]) 同步地将数据追加到文件,如果文件尚不存在则创建该文件。 data 可以是字符串或 Buffer

      // 直接针对文件追加
      fs.appendFileSync('文件.txt', '追加的数据', 'utf8');
      
      // 先打开文件,然后针对文件描述符追加
      fd = fs.openSync('文件.txt', 'a');
      fs.appendFileSync(fd, '追加的数据', 'utf8');
      
      • fs.copyFile(src, dest[, mode], callback) 异步地将 src 拷贝到 dest。 默认情况下,如果 dest 已经存在,则覆盖它。 除了可能的异常,回调函数没有其他参数。 Node.js 不保证拷贝操作的原子性。 如果在打开目标文件用于写入后发生错误,则 Node.js 将尝试删除目标文件。 fs.copyFileSync(src, dest[, mode]) 同步
      • fs.rename(oldPath, newPath, callback) 异步地把 oldPath 文件重命名为 newPath 提供的路径名。 如果 newPath 已存在,则覆盖它。 除了可能的异常,完成回调没有其他参数。 fs.renameSync(oldPath, newPath) 同步版本,文件重命名
      • fs.write(fd, buffer[, offset[, length[, position]]], callback) 写入 bufferfd 指定的文件。不等待回调就对同一个文件多次使用 fs.write() 是不安全的。 对于这种情况,建议使用 fs.createWriteStream()

      示例

      获取目录下所有图片,并上传到服务器

      const FormData = require('form-data');
      const fetch = require('node-fetch');
      var fs = require('fs');
      var path = require('path');
      var dirPath = 'cdn-transform'
      var filePath = path.resolve(`./node-upload-img/${dirPath}`);
      //文件遍历方法
      
      function fileDisplay(filePath){
          fs.readdir(filePath,function(err,files){
              if(err){
                  console.warn(err)
              }else{
                  files.forEach(function(filename){
                      var filedir = path.join(filePath, filename);
                      var buffer = fs.readFileSync(filedir)
                      var formData = new FormData();
                      var fileName = `${filename}`
                      var url =  `www.lilnong.top/upload/fe-up/${dirPath}/${fileName}`;
                      formData.append('file', buffer, `${fileName}`);
                      fetch('http://www.lilnong.top/upload',{
                          headers: formData.getHeaders(),
                          method: 'post',
                          body: formData
                      })
                      .then(v=>v.text())
                      .then(v=>{
                          console.log('http://www.lilnong.top/upload', url, v)
                      })
                  })
              }
          });
      }
      fileDisplay(filePath)
      

      递归遍历所有json

      const readDir = (entry, paths = []) => {
          const dirInfo = fs.readdirSync(entry);
          dirInfo.forEach(item=>{
              const location = path.join(entry,item);
              const info = fs.statSync(location);
              if(info.isDirectory()){
                  console.log(`dir:${location}`);
                  readDir(location, [item]);
              }else{
                  if(/.json$/.test(location)){
                      readFile(location, paths)
                  }
              }
          })
      }
      console.log('__dirname', __dirname)
      readDir(__dirname);
      
      function readFile(path, pathKey){
          return console.log(path, pathKey);
      })
      

      微信公众号:前端linong

      参考文献


      原文:SegmentFault

评论

评论功能即将开放。你也可以通过 邮件GitHub Issue 反馈。