源私有檔案系統
注意:此功能在 Web Workers 中可用。
源私有檔案系統(OPFS)是作為檔案系統 API 的一部分提供的儲存端點,它僅對頁面的源可見,不像常規檔案系統那樣對使用者可見。它提供了對一種特殊檔案的訪問,這種檔案在效能方面經過高度最佳化,並允許對其內容進行原地寫入訪問。
使用檔案系統訪問 API 處理檔案
擴充套件了檔案系統 API 的檔案系統訪問 API,透過選擇器方法提供對檔案的訪問。例如:
Window.showOpenFilePicker()允許使用者選擇要訪問的檔案,這將返回一個FileSystemFileHandle物件。- 呼叫
FileSystemFileHandle.getFile()來訪問檔案內容,然後使用FileSystemFileHandle.createWritable()/FileSystemWritableFileStream.write()修改內容。 - 使用
FileSystemHandle.requestPermission({mode: 'readwrite'})請求使用者儲存更改的許可權。 - 如果使用者接受許可權請求,更改將儲存回原始檔案。
這可行,但有一些限制。這些更改是針對使用者可見的檔案系統進行的,因此存在許多安全檢查(例如,Chrome 中的安全瀏覽)來防止惡意內容寫入該檔案系統。這些寫入不是原地進行的,而是使用臨時檔案。原始檔案只有在透過所有安全檢查後才會被修改。
因此,這些操作相當緩慢。在進行小的文字更新時,這不太明顯,但在進行更重大的、大規模的檔案更新時,例如SQLite 資料庫修改時,效能會受到影響。
OPFS 如何解決這些問題?
OPFS 提供低級別的、逐位元組的檔案訪問,它僅對頁面的源可見,對使用者不可見。因此,它不需要同樣一系列的安全檢查和許可權授予,從而比檔案系統訪問 API 呼叫更快。它還提供了一組同步呼叫(其他檔案系統 API 呼叫是非同步的),這些呼叫只能在 Web Worker 中執行,以免阻塞主執行緒。
總結 OPFS 與使用者可見檔案系統的區別
- 與任何其他源分割槽儲存機制(例如IndexedDB API)一樣,OPFS 受瀏覽器儲存配額限制。您可以透過
navigator.storage.estimate()訪問 OPFS 使用的儲存空間量。 - 清除網站的儲存資料會刪除 OPFS。
- 訪問 OPFS 中的檔案不需要許可權提示和安全檢查。
- 瀏覽器將 OPFS 的內容持久化到磁碟的某個位置,但您不能期望找到一一對應的建立的檔案。OPFS 不打算對使用者可見。
如何訪問 OPFS?
要訪問 OPFS,首先需要呼叫navigator.storage.getDirectory() 方法。這將返回一個FileSystemDirectoryHandle 物件引用,該物件代表 OPFS 的根目錄。
在主執行緒中操作 OPFS
從主執行緒訪問 OPFS 時,將使用非同步的、基於Promise 的 API。透過在代表 OPFS 根目錄(以及建立的子目錄)的FileSystemDirectoryHandle 物件上呼叫FileSystemDirectoryHandle.getFileHandle() 和FileSystemDirectoryHandle.getDirectoryHandle(),可以分別訪問檔案(FileSystemFileHandle)和目錄(FileSystemDirectoryHandle)控制代碼。
注意:將 { create: true } 傳遞給上述方法會導致在檔案或資料夾不存在時建立它們。
// Create a hierarchy of files and folders
const fileHandle = await opfsRoot.getFileHandle("my first file", {
create: true,
});
const directoryHandle = await opfsRoot.getDirectoryHandle("my first folder", {
create: true,
});
const nestedFileHandle = await directoryHandle.getFileHandle(
"my first nested file",
{ create: true },
);
const nestedDirectoryHandle = await directoryHandle.getDirectoryHandle(
"my first nested folder",
{ create: true },
);
// Access existing files and folders via their names
const existingFileHandle = await opfsRoot.getFileHandle("my first file");
const existingDirectoryHandle =
await opfsRoot.getDirectoryHandle("my first folder");
讀取檔案
- 呼叫
FileSystemDirectoryHandle.getFileHandle()返回一個FileSystemFileHandle物件。 - 呼叫
FileSystemFileHandle.getFile()物件返回一個File物件。這是一種特殊的Blob型別,因此可以像任何其他Blob一樣對其進行操作。例如,您可以透過Blob.text()直接訪問文字內容。
寫入檔案
- 呼叫
FileSystemDirectoryHandle.getFileHandle()返回一個FileSystemFileHandle物件。 - 呼叫
FileSystemFileHandle.createWritable()返回一個FileSystemWritableFileStream物件,這是一種特殊的WritableStream型別。 - 使用
FileSystemWritableFileStream.write()呼叫向其中寫入內容。 - 使用
WritableStream.close()關閉流。
刪除檔案或資料夾
您可以對父目錄呼叫FileSystemDirectoryHandle.removeEntry(),並將要刪除的項的名稱作為引數傳遞。
directoryHandle.removeEntry("my first nested file");
您還可以對代表要刪除的項的FileSystemFileHandle 或FileSystemDirectoryHandle 呼叫FileSystemHandle.remove()。要刪除一個資料夾及其所有子資料夾,請傳遞 { recursive: true } 選項。
await fileHandle.remove();
await directoryHandle.remove({ recursive: true });
以下提供了一種快速清空整個 OPFS 的方法
await (await navigator.storage.getDirectory()).remove({ recursive: true });
列出資料夾內容
FileSystemDirectoryHandle 是一個非同步迭代器。因此,您可以使用 for await...of 迴圈和標準方法(如entries()、values() 和keys())對其進行迭代。
例如
for await (let [name, handle] of directoryHandle) {
}
for await (let [name, handle] of directoryHandle.entries()) {
}
for await (let handle of directoryHandle.values()) {
}
for await (let name of directoryHandle.keys()) {
}
在 Web Worker 中操作 OPFS
Web Workers 不會阻塞主執行緒,這意味著您可以在此上下文中執行同步檔案訪問 API。同步 API 更快,因為它們避免了處理 Promise。
您可以透過在常規FileSystemFileHandle 上呼叫FileSystemFileHandle.createSyncAccessHandle() 來同步訪問檔案。
注意:儘管其名稱中包含“Sync”(同步),但 createSyncAccessHandle() 方法本身是非同步的。
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("my-high-speed-file.txt", {
create: true,
});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
在返回的FileSystemSyncAccessHandle 上有許多*同步*方法可用:
getSize():返回檔案的大小(以位元組為單位)。write():將緩衝區的內容寫入檔案,可以選擇在給定偏移量處寫入,並返回寫入的位元組數。檢查返回的寫入位元組數允許呼叫者檢測和處理錯誤及部分寫入。read():將檔案內容讀取到緩衝區中,可以選擇在給定偏移量處讀取。truncate():將檔案大小調整為給定大小。flush():確保檔案內容包含透過write()進行的所有修改。close():關閉訪問控制代碼。
以下是一個使用上述所有方法的示例
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("fast", { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
// Initialize this variable for the size of the file.
let size;
// The current size of the file, initially `0`.
size = accessHandle.getSize();
// Encode content to write to the file.
const content = textEncoder.encode("Some text");
// Write the content at the beginning of the file.
accessHandle.write(content, { at: size });
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `9` (the length of "Some text").
size = accessHandle.getSize();
// Encode more content to write to the file.
const moreContent = textEncoder.encode("More content");
// Write the content at the end of the file.
accessHandle.write(moreContent, { at: size });
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `21` (the length of
// "Some textMore content").
size = accessHandle.getSize();
// Prepare a data view of the length of the file.
const dataView = new DataView(new ArrayBuffer(size));
// Read the entire file into the data view.
accessHandle.read(dataView, { at: 0 });
// Logs `"Some textMore content"`.
console.log(textDecoder.decode(dataView));
// Read starting at offset 9 into the data view.
accessHandle.read(dataView, { at: 9 });
// Logs `"More content"`.
console.log(textDecoder.decode(dataView));
// Truncate the file after 4 bytes.
accessHandle.truncate(4);
瀏覽器相容性
載入中…