在Web环境中,理论上是无法轻易实现直接对系统级文件进行访问和修改的,因为浏览器本身就是个隔离容器,这个容器和系统层级的交互,只能通过官方API去打通,因此官方推出了File System API,这套API较新存在兼容性问题,在使用时需做好兼容处理。
概念
File System API 使用了面向对象的概念,把系统文件映射成了js对象,在我们对系统文件的日常操作上,一般都是文件夹内,存放文件。而在系统底层,文件夹本身也是文件,就是一种特殊文件类型的文件而已,因此File System API 也对文件夹和文件的概念,提供了2种文件对象,分别对应
FileSystemDirectoryHandle
和FileSystemFileHandle
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 方法,这个方法的参数就是你要写入回硬盘的内容,支持
ArrayBuffer
、Blob
、string
,当然这个方法的调用,本质上不是直接写入硬盘,而且是先写入内存,然后再通过调用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
FileSystemDirectoryHandle
和FileSystemFileHandle
对象的获取
官方描述如下
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)查询