# Origin Private File System（OPFS，源私有文件系统）

> navigator.storage.getDirectory() 如何暴露一个按源隔离、用户不可见的文件存储端点，仅能在专用 Worker（dedicated Web Worker）内使用的同步 FileSystemSyncAccessHandle 方法，以及自 2023 年 3 月起 Baseline 广泛可用的浏览器支持状况。

**一句话：** 根据 MDN，"origin private file system（OPFS，源私有文件系统）是 File System API
提供的一个存储端点，它私有于页面所在的源，且不像常规文件系统那样对用户可见"，并提供"对其内容的
原地写入访问"。

## 与用户可见文件系统的区别

MDN 总结了 OPFS 与 File System Access API 所操作的用户可见文件之间的若干区别：OPFS "与其他任何
按源分区的存储机制（例如 IndexedDB API）一样，受浏览器存储配额限制"；可以通过
`navigator.storage.estimate()` 查看其用量；"清除站点的存储数据会删除 OPFS"；"访问 OPFS 中的文件
不需要权限提示或安全检查"；以及"OPFS 并非设计为对用户可见"。MDN 还指出 OPFS 提供"低层级、逐字节
的文件访问"，并且由于跳过了用户可见 File System Access API 所需的选择器/权限安全检查，"因而速度
更快"。

## 访问 OPFS 根目录

```js
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("my-file.txt", { create: true });
```

MDN 指出："要首先访问 OPFS，需要调用 `navigator.storage.getDirectory()` 方法。该方法返回一个
`FileSystemDirectoryHandle` 对象的引用，代表 OPFS 的根目录。" 在主线程中，文件与目录通过异步、
基于 Promise 的 `FileSystemDirectoryHandle.getFileHandle()` 与 `getDirectoryHandle()` 访问，传入
`{ create: true }` "会在文件或文件夹不存在时创建它"。

## 在专用 Worker（dedicated Web Worker）中同步访问

MDN 解释道，OPFS "还提供了一组同步调用（File System API 的其他调用均为异步）"以提升性能，并且
所返回的 `FileSystemSyncAccessHandle` "仅能在专用 Worker（dedicated Web Worker）内访问"，从而使
其同步方法不会阻塞主线程执行。尽管名称中带有 "Sync"，MDN 指出"`createSyncAccessHandle()` 方法
本身是异步的"：

```js
// 在专用 Worker（dedicated Web Worker）内
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("fast", { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();

const content = new TextEncoder().encode("Some text");
accessHandle.write(content, { at: 0 });
accessHandle.flush();
accessHandle.close();
```

MDN 列出了返回的 `FileSystemSyncAccessHandle` 上的同步方法：`getSize()`（"返回文件的字节大小"）、
`write()`（"将缓冲区内容写入文件，可选择在给定偏移处写入，并返回已写入的字节数"）、`read()`
（"将文件内容读取到缓冲区，可选择从给定偏移处读取"）、`truncate()`（"将文件大小调整为给定
大小"）、`flush()`（"确保文件内容包含通过 `write()` 完成的所有修改"）、以及 `close()`
（"关闭该访问句柄"）。

## 运行时检测与回退

```js
async function openOpfsFile(name) {
  if (!("storage" in navigator) || !("getDirectory" in navigator.storage)) {
    // 此处不支持 OPFS——改用 IndexedDB 或内存中存储作为回退方案。
    return null;
  }
  const opfsRoot = await navigator.storage.getDirectory();
  return opfsRoot.getFileHandle(name, { create: true });
}
```

## 浏览器支持

MDN 将 OPFS 标记为 **Baseline：广泛可用**，指出"自 2023 年 3 月起，它已在各浏览器中可用"。MDN
还将其标注为安全上下文特性（"仅在安全上下文（HTTPS）中可用，视支持的浏览器而定"）。同步的
`FileSystemSyncAccessHandle` 方法则根据 MDN，"仅能在专用 Worker（dedicated Web Worker）内访问"。

## 实用清单

- [ ] 使用前先做 `navigator.storage?.getDirectory` 特性检测，即便 MDN 将 OPFS 标记为自 2023 年
      3 月起 Baseline 广泛可用——较旧或非浏览器环境可能仍不具备它。
- [ ] 通过 HTTPS 提供页面；MDN 将 OPFS 记录为仅限安全上下文的特性。
- [ ] 仅在专用 Worker（dedicated Web Worker）内使用同步的 `FileSystemSyncAccessHandle` 方法
      （`read()`、`write()`、`flush()`、`close()`）——MDN 指出该类"仅能在专用 Worker（dedicated
      Web Worker）内访问"，以避免阻塞主线程。共享 Worker（shared worker）与 Service Worker
      不受支持。
- [ ] 记住 `createSyncAccessHandle()` 本身返回一个 Promise，尽管其名称中带有 "Sync"（根据
      MDN）。
- [ ] 不要指望 OPFS 中的内容对用户可见，也不要指望它能在"清除存储数据"操作后存活——MDN 指出
      清除站点存储会删除 OPFS，且它"并非设计为对用户可见"。
- [ ] 为浏览器存储配额限制预留余量，因为 MDN 指出 OPFS "与其他任何按源分区的存储机制一样，受
      浏览器存储配额限制"。

## 相关参考

- [Persistence, quotas, and eviction](/zh/reference/storage/persistence/)
- [File System Access API](/zh/reference/capabilities/file-system-access/)