File System API 实现 Web级系统文件读写

在Web环境中,理论上是无法轻易实现直接对系统级文件进行访问和修改的,因为浏览器本身就是个隔离容器,这个容器和系统层级的交互,只能通过官方API去打通,因此官方推出了File System API,这套API较新存在兼容性问题,在使用时需做好兼容处理。

概念

File System API 使用了面向对象的概念,把系统文件映射成了js对象,在我们对系统文件的日常操作上,一般都是文件夹内,存放文件。而在系统底层,文件夹本身也是文件,就是一种特殊文件类型的文件而已,因此File System API 也对文件夹和文件的概念,提供了2种文件对象,分别对应FileSystemDirectoryHandleFileSystemFileHandle

FileSystemFileHandle 对象(读取系统文件)

FileSystemFileHandle 是一个系统文件信息的描述对象,官方称这个对象为文件句柄对象,说人话就是( 这个对象是一个当前选择文件对象和系统之间的映射对象,当你使用 FileSystemFileHandle实例.getFile()后就可以得到当前句柄对象指向的文件所属的File对象 )File对象到手,怎么玩就考验基本功了😀

html 复制代码
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<button onclick="selectFile()">选择文件</button>
<body>
<script>
  const selectFile = async ()=>{
    //window.showOpenFilePicker 会打开一个文件选择对话框,用户选择文件后,将返回一个 FileSystemFileHandle数组
    const FileSystemFileHandleInstanceArray = await window.showOpenFilePicker()
    //这里只取第一个
    const FileSystemFileHandleInstance = FileSystemFileHandleInstanceArray[0]
    //通过FileSystemFileHandle的getFile方法获取File对象
    const FileInstance = await  FileSystemFileHandleInstance.getFile()
    console.log(FileInstance)
    //File {name: 'test_image.png', lastModified: 1711421687929, lastModifiedDate: Tue Mar 26 2024 10:54:47 GMT+0800 (中国标准时间), webkitRelativePath: '', size: 42763, ...}
  }
</script>
</body>
</html>

FileSystemFileHandle 对象(写入系统文件)

前面说了FileSystemFileHandle 读取,但是怎么样把修改后的文件数据写回硬盘呢?那就要再谈到句柄对象 的概念了,既然可以通过这个对象拿到系统中的文件对象,那么这个对象,对文件对应的硬盘所处位置的映射关系肯定也是知道的,所以,不出意料的这个对象提供一个方法createWritable 这个方法会返回一个Promise,这个Promise成功的兑换结果就是一个写入器对象,这个对象提供一个write 方法,这个方法的参数就是你要写入回硬盘的内容,支持 ArrayBufferBlobstring,当然这个方法的调用,本质上不是直接写入硬盘,而且是先写入内存,然后再通过调用close方法来完成写入

html 复制代码
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<button onclick="selectFile()">选择文件</button>
<body>
<script>
  const selectFile = async ()=>{
    //window.showOpenFilePicker 会打开一个文件选择对话框,用户选择文件后,将返回一个 FileSystemFileHandle数组
    const FileSystemFileHandleInstanceArray = await window.showOpenFilePicker()
    //这里只取第一个
    const FileSystemFileHandleInstance = FileSystemFileHandleInstanceArray[0]
    //通过FileSystemFileHandle的getFile方法获取File对象
    const FileInstance = await  FileSystemFileHandleInstance.getFile()
    //通过File的text方法读取文件内容
    let FileInstanceContent = await FileInstance.text()
    //对文件内容进行修改
    FileInstanceContent += 'new Content'
    //通过FileSystemFileHandle的createWritable方法创建一个可写的文件
    const writable = await FileSystemFileHandleInstance.createWritable()
    //通过可写文件的write方法写入文件内容
    await writable.write(FileInstanceContent)
    //通过可写文件的close方法关闭文件
    await writable.close()
  }
</script>
</body>
</html>

FileSystemDirectoryHandle 对象

FileSystemDirectoryHandle 对象映射的就是文件夹对象,像一个盒子,可以通过调用FileSystemDirectoryHandle.entries()来获取一个异步迭代器,这个异步迭代器里面存放的就是他的下属文件的名称和FileSystemFileHandle 对象,如果下属文件存在文件夹嵌套,则存放的是文件名和FileSystemDirectoryHandle对象。

比如以这样的目录结构为事例

html 复制代码
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<button onclick="selectFile()">选择文件</button>
<body>
<script>
  const selectFile = async ()=>{
    //window.showDirectoryPicker 会打开一个对话框,用户选择文件夹后,将返回一个 FileSystemDirectoryHandle 对象
    const FileSystemDirectoryHandle = await window.showDirectoryPicker()
    const FileSystemDirectoryHandleAsyncIterator =  FileSystemDirectoryHandle.entries()
    //使用异步迭代器去读取
    for await (const [fileName,FileSystemFileHandleOrFileSystemDirectoryHandle ] of FileSystemDirectoryHandleAsyncIterator) {
      console.log(FileSystemFileHandleOrFileSystemDirectoryHandle)
      //FileSystemFileHandle{kind: 'file', name: 'test_image.png'}
      //FileSystemDirectoryHandle{kind: 'directory', name: '子测试文件夹'}
    }
  }
</script>
</body>
</html>

可以发现不论是FileSystemFileHandle 还是FileSystemDirectoryHandle 都会有一个属性kind用来标记文件类型,就和开篇说的一样,文件夹本质就是一种特殊的文件,所以可以结合这一规律,自行通过递归,来实现一个目录树的生成。

html 复制代码
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<button onclick="selectFile()">选择文件</button>
<body>
<script>
  const selectFile = async ()=>{
    //window.showDirectoryPicker 会打开一个对话框,用户选择文件夹后,将返回一个 FileSystemDirectoryHandle 对象
    const FileSystemDirectoryHandle = await window.showDirectoryPicker()
    const fileTree = await createDirectoryTree(FileSystemDirectoryHandle)
    console.log(fileTree)
  }
  /**
   * @Description:通过 FileSystemDirectoryHandle创建目录树的功能函数
   * @author 莫若省
   * @createTime 2024/3/26
  */

  const createDirectoryTree = async (FileSystemDirectoryHandle)=>{
    FileSystemDirectoryHandle.children=[]
    const FileSystemDirectoryHandleAsyncIterator =  FileSystemDirectoryHandle.entries()
    for await (const [fileName,FileSystemFileHandleOrFileSystemDirectoryHandle ] of FileSystemDirectoryHandleAsyncIterator) {
      if(FileSystemFileHandleOrFileSystemDirectoryHandle.kind === 'directory'){
        FileSystemDirectoryHandle.children.push(await createDirectoryTree(FileSystemFileHandleOrFileSystemDirectoryHandle))
      }else{
        FileSystemDirectoryHandle.children.push(FileSystemFileHandleOrFileSystemDirectoryHandle)
      }
    }
    return FileSystemDirectoryHandle
  }
</script>
</body>
</html>
js 复制代码
 FileSystemDirectoryHandle {children: Array(3), kind: 'directory', name: '测试文件夹'}
   children: Array(3)
        FileSystemFileHandle {kind: 'file', name: '.DS_Store'}
        FileSystemFileHandle {kind: 'file', name: 'test_image.png'}
        FileSystemDirectoryHandle
        kind: "directory"
        name: "子测试文件夹"
        children: Array(1)
             FileSystemFileHandle {kind: 'file', name: '未命名.txt'}
             length: 1
             [[Prototype]]: Array(0)
             [[Prototype]]: FileSystemDirectoryHandle
        length: 3
        [[Prototype]]: Array(0)
    kind: "directory"
    name: "测试文件夹"
    [[Prototype]]: FileSystemDirectoryHandle

FileSystemDirectoryHandleFileSystemFileHandle对象的获取

官方描述如下

FileSystemDirectoryHandle 接口提供指向一个文件系统目录的句柄。 这个接口可以通过 window.showDirectoryPicker()StorageManager.getDirectory() (en-US)DataTransferItem.getAsFileSystemHandle() (en-US)FileSystemDirectoryHandle.getDirectoryHandle() 这些方法访问获得。

FileSystemFileHandle 接口表示一个指向文件系统条目的句柄。可通过 window.showOpenFilePicker() 方法来访问此接口。

可以通过上述方法调用,获取文件句柄对象得到文件对象后实现业务功能。 此外File System API 还提供了其他文件操作相关API具体可以通过文件系统 API - Web API 接口参考 | MDN (mozilla.org)查询

相关推荐
10年前端老司机2 分钟前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~5 分钟前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客1 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2451 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇6 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖6 小时前
http的缓存问题
前端·javascript·http
小小小小宇6 小时前
请求竞态问题统一封装
前端
loriloy6 小时前
前端资源帖
前端
源码超级联盟6 小时前
display的block和inline-block有什么区别
前端
GISer_Jing7 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js