你不得不清楚的 Javascript 文件系统
你好,我是泰罗凹凸曼,最近在使用几个软件的时候,发现了一些很有意思的文件相关 API,可以直接操作电脑上的文件并且进行修改,今天我们就简单梳理一下 Javascript 文件系统的相关知识。
我们可以先尝试下这几个软件,前两个是常用的绘图软件,后面是我们比较熟悉的 VSCode 编辑器。无一例外,他们都支持直接打开本地文件,而且可以直接对文件进行修改。
# 选择文件
我们平时在选择文件的时候,是通过如下 <input type='file'/>
来选择文件的,接着通过 FileReader 之类的 API 来获取文件内容,随着现在 Web 的发展,我们可以直接通过内置的 window.showOpenFilePicker
来直接打开文件的选择框。
window.showOpenFilePicker().then((fileHandle) => {
console.log(fileHandle);
});
// 我们也可以使用 showDirectoryPicker 来选择文件夹
window.showDirectoryPicker().then((fileHandle) => {
console.log(fileHandle);
});
2
3
4
5
6
7
8
showOpenFilePicker
和 showDirectoryPicker
都可以接受一个 options 选项,可以指定的内容如下:
- multiple:是否可以多选
- excludeAcceptAllOption:是否显示所有文件选项,默认的选择器 UI 是禁止掉未指定的类型的,这个字段可以控制是否彻底不允许选择
- 值为 true,不允许选择在 types 中未指定的文件类型
- 值为 false,允许选择在 types 中未指定的文件类型,可以通过
选项
按钮中的所有文件选择其他文件类型
- types:指定文件类型,Array 类型
- description:文件类型描述
- accept:文件类型,Object 类型
- image/*: ['.png', '.jpg'] // 指定文件类型和后缀名
如下简单是选择文件的简单示例:
/**
* 如果需要限制文件选择的类型,必须通过这种方式去指定,直接通过后缀名的方式是不能做到限制的!!
* 'image/png': ['.png'],
* 'image/jpeg': ['.jpg'],
*/
// 返回的 fileHandle 是一个 FileSystemFileHandle 类型
const fileHandle = await window.showOpenFilePicker({
multiple: false,
excludeAcceptAllOption: true,
types: [
{
description: 'Images',
accept: {
'image/png': ['.png'],
'image/jpeg': ['.jpg'],
},
},
],
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
我们可以通过 FileSystemFileHandle
来获取文件的信息,如下:
const fileData = await FileSystemFileHandle.getFile()
// 这里的 fileData 就是一个普通的 File 对象
// 和 input[type='file'] 选择的文件一样
2
3
4
接着,我们就可以使用 FileReader
API 来进行文件的内容读取了!
# 降级方案
window.showOpenFilePicker
是一个新的 API,目前的支持度很低:
那么在不支持这个 API 的浏览器上,我们依然可以通过降级到 input
来实现文件的选择!
function openFile(options) {
if (window.showOpenFilePicker) {
return window.showOpenFilePicker(options)
}
return new Promise((resolve, reject) => {
const input = document.createElement('input')
input.type = 'file'
input.multiple = options.multiple
input.accept = options.types[0].accept
input.onchange = () => {
resolve(input.files)
}
input.click()
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 读取文件内容
我们可以使用 FileReader 来实现对文件的读取,一般情况下,我们需要处理两种文件类型的读取
- 文本类型,如代码文件,文本文件,如后缀为 .vue, .js, .txt 等都属于文本类型
- 图片类型,一般用于选择文件后进行回显
FileReader 的初始化非常简单:
const reader = new FileReader()
我们可以通过 reader 的方法来读取文件内容,如下:
- readAsArrayBuffer:读取文件内容为 ArrayBuffer
- readAsBinaryString:读取文件内容为二进制字符串
- readAsDataURL:读取文件内容为 DataURL,用于读取图片类型的文件,可以直接用于 img 的 src 属性
- readAsText:读取文件内容为文本,用于读取文本类型
所以,我们读取文本或者图片的方法可以分别写成如下形式:
// 读取文本
function readText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
resolve(reader.result)
}
reader.onerror = reject
reader.readAsText(file)
})
}
// 读取图片
function readImage(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
resolve(reader.result)
}
reader.onerror = reject
reader.readAsDataURL(file)
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 保存文件
我们可以利用利用上面返回的 FileSystemFileHandle
来进行文件的保存,如下:
const writable = await fileHandle.createWritable()
await writable.write('Hello World')
await writable.close()
2
3
如果我们需要保存的文件是一个新的文件,那么我们可以通过 window.showSaveFilePicker
来进行文件的保存,如下:
const fileHandle = await window.showSaveFilePicker({
types: [
{
description: 'Text files',
accept: {
'text/plain': ['.txt'],
},
},
],
})
const writable = await fileHandle.createWritable()
await writable.write('Hello World')
await writable.close()
2
3
4
5
6
7
8
9
10
11
12
13
14
# 文件操作
目前,Web API 还不支持文件的重命名,移动,复制,删除等操作,但是,我们可以通过 FileSystemDirectoryHandle
来实现这些操作,但是这也意味着,如果你需要对文件做复杂的操作的话,不支持该 API 的浏览器是无法实现的!也无法降级处理,如下是 VSCode 在 Safari 上的表现:
从选择一个文件夹开始:
async function openDir() {
const dirHandle = await window.showDirectoryPicker()
// 获取文件夹下的所有文件
for await (let [name, handle] of dirHandle.entries()) {
// name: 文件名称
// handle: 文件的处理器
console.log(name, handle, handle.kind === 'file' ? '文件' : '文件夹')
}
}
2
3
4
5
6
7
8
9
10
我们可以通过递归获取文件的处理器和所有的文件树,每一个文件有一个自己独有的 FileSystemFileHandle
,我们可以通过这个对象来进行文件的操作。
# 创建文件
创建文件非常简单,我们必须具备一个文件夹的处理器,然后通过 getFileHandle
方法来创建一个新的文件,如下:
// dirHandle 是一个 FileSystemDirectoryHandle 对象,我们可以通过 window.showDirectoryPicker() 来获取
// 也可以在遍历文件夹的过程中进行缓存
const fileHandle = await dirHandle.getFileHandle('new-file.txt', { create: true })
2
3
4
# 删除文件
删除文件也非常简单,我们只需要通过 removeEntry
方法来删除文件,如下:
await dirHandle.removeEntry('new-file.txt')
# 移动文件
移动文件没有直接的 API 支持,但是只要我们清楚移动文件的本质的话,就可以很容易的实现这个功能,如下:
- 获取需要移动的位置的文件夹的处理器
- 获取需要移动的文件的处理器
- 通过
getFileHandle
方法来创建一个新的文件,将旧文件的内容复制到新文件中 - 通过
removeEntry
方法来删除旧的文件
/**
* 移动文件
* @param dirHandle 源文件夹句柄
* @param destDirHandle 目标文件夹句柄
* @param filename 文件名
* @param destFileName 目标文件名
*/
async function moveFile(dirHandle, destDirHandle, filename, destFileName) {
try {
// 获取源文件句柄
const srcFileHandle = await dirHandle.getFileHandle(filename)
// 在目标目录创建新文件句柄
const destFileHandle = await destDirHandle.getFileHandle(destFileName, { create: true })
// 读取源文件内容
const file = await srcFileHandle.getFile();
const contents = await file.arrayBuffer();
// 将内容写入目标文件
const writableStream = await destFileHandle.createWritable();
await writableStream.write(contents);
await writableStream.close();
// 删除源文件
await dirHandle.removeEntry(filename);
console.log(`成功将文件 ${filename} 移动到目标目录`);
} catch (error) {
console.error('移动文件失败:', error);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 重命名文件
Web 没有直接支持重命名文件的 API,所以我们如上,也是通过 getFileHandle
方法来创建一个新的文件,将旧文件的内容复制到新文件中,然后删除旧文件,直接去调用我们移动文件的方法即可:
moveFile(dirHandle, dirHandle, 'old-file.txt', 'new-file.txt')
文件相关的 API 都可以在这里找到:MDN (opens new window)
# 总结
通过上面的内容,我们可以知道,通过 Web API,我们可以直接实现文件的选择,读取,保存,这样就可以实现一些简单的文件操作了,但是,这些 API 还是有一些限制的,比如:
- 必须在安全的环境中操作,如 https,而且操作本地文件是需要经过用户同意的
- 浏览器支持率很低,一般只在 Chrome 能得到良好的支持
如果你需要无缝的使用这些功能,可以使用 Chrome 开源的 browser-fs-access (opens new window)
通过上面的浏览器 API,你可以写出一个简单的浏览器编辑器吗?去探索,不知道的东西还多着呢!
我是泰罗凹凸曼,M78 星云最爱写代码的,我们下一篇再会!