你不得不清楚的 Javascript 文件系统

lxf2023-12-14 20:50:01

你好,我是泰罗凹凸曼,最近在使用几个软件的时候,发现了一些很有意思的文件相关API,可以直接操作电脑上的文件并且进行修改,今天我们就简单梳理一下 Javascript 文件系统的相关知识。

我们可以先尝试下这几个软件,前两个是常用的绘图软件,后面是我们比较熟悉的 VSCode 编辑器。无一例外,他们都支持直接打开本地文件,而且可以直接对文件进行修改。

  • excalidraw
  • drawio
  • vscode

你不得不清楚的 Javascript 文件系统

选择文件

我们平时在选择文件的时候,是通过如下 <input type='file'/> 来选择文件的,接着通过 FileReader 之类的 API 来获取文件内容,随着现在 Web 的发展,我们可以直接通过内置的 window.showOpenFilePicker 来直接打开文件的选择框。

window.showOpenFilePicker().then((fileHandle) => {
  console.log(fileHandle);
});

// 我们也可以使用 showDirectoryPicker 来选择文件夹
window.showDirectoryPicker().then((fileHandle) => {
  console.log(fileHandle);
});

showOpenFilePickershowDirectoryPicker 都可以接受一个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'],
      },
    },
  ],
})

我们可以通过 FileSystemFileHandle 来获取文件的信息,如下:

const fileData = await FileSystemFileHandle.getFile()

// 这里的 fileData 就是一个普通的 File 对象
// 和 input[type='file'] 选择的文件一样

接着,我们就可以使用 FileReader API 来进行文件的内容读取了!

降级方案

window.showOpenFilePicker 是一个新的 API,目前的支持度很低:

你不得不清楚的 Javascript 文件系统

那么在不支持这个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()
  })
}

读取文件内容

我们可以使用 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)
  })
}

保存文件

我们可以利用利用上面返回的 FileSystemFileHandle 来进行文件的保存,如下:

const writable = await fileHandle.createWritable()
await writable.write('Hello World')
await writable.close()

如果我们需要保存的文件是一个新的文件,那么我们可以通过 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()

文件操作

目前,Web API 还不支持文件的重命名,移动,复制,删除等操作,但是,我们可以通过 FileSystemDirectoryHandle 来实现这些操作,但是这也意味着,如果你需要对文件做复杂的操作的话,不支持该API 的浏览器是无法实现的!也无法降级处理,如下是 VSCode 在 Safari 上的表现:

你不得不清楚的 Javascript 文件系统

从选择一个文件夹开始:

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' ? '文件' : '文件夹')
  }
}

我们可以通过递归获取文件的处理器和所有的文件树,每一个文件有一个自己独有的 FileSystemFileHandle,我们可以通过这个对象来进行文件的操作。

创建文件

创建文件非常简单,我们必须具备一个文件夹的处理器,然后通过 getFileHandle 方法来创建一个新的文件,如下:

// dirHandle 是一个 FileSystemDirectoryHandle 对象,我们可以通过 window.showDirectoryPicker() 来获取
// 也可以在遍历文件夹的过程中进行缓存

const fileHandle = await dirHandle.getFileHandle('new-file.txt', { create: true })

删除文件

删除文件也非常简单,我们只需要通过 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);
  }
}

重命名文件

Web没有直接支持重命名文件的API,所以我们如上,也是通过 getFileHandle 方法来创建一个新的文件,将旧文件的内容复制到新文件中,然后删除旧文件,直接去调用我们移动文件的方法即可:

moveFile(dirHandle, dirHandle, 'old-file.txt', 'new-file.txt')

文件相关的API都可以在这里找到:MDN

总结

通过上面的内容,我们可以知道,通过 Web API,我们可以直接实现文件的选择,读取,保存,这样就可以实现一些简单的文件操作了,但是,这些 API 还是有一些限制的,比如:

  • 必须在安全的环境中操作,如 https,而且操作本地文件是需要经过用户同意的
  • 浏览器支持率很低,一般只在 Chrome 能得到良好的支持

如果你需要无缝的使用这些功能,可以使用 Chrome 开源的 browser-fs-access

通过上面的浏览器API,你可以写出一个简单的浏览器编辑器吗?去探索,不知道的东西还多着呢!

我是泰罗凹凸曼,M78星云最爱写代码的,我们下一篇再会!

本文正在参加「」

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!