從前,網頁中想要實現文件的讀寫是比較麻煩的,爲了讀取一個文件,咱們須要建立一個
type="file"
的input
標籤讓用戶選擇一個文件,而爲了保存一些內容到本地硬盤,咱們則須要使用a
標籤的download
屬性去觸發文件保存。javascript而連續的文件保存則沒法實現了,由於向本地硬盤寫入文件是必定要觸發「保存」彈框的。要遍歷一個本地的目錄也是沒法實現的。java
在最新版的 Chrome 78 中,Google 開啓了一項實驗性的特性:Native File System,這是一個全新的本地文件讀寫 API,當前還未開發完成,所以默認並無開啓這項功能。git
若是想要體驗這個 API 的話,能夠在 chrome://flags 這個地址中搜索 Native File System API
進行啓用。github
關於這個 API 的簡介,能夠參考:github.com/WICG/native…chrome
這是一個全新的文件讀寫 API,它帶來了更加方便的本地文件讀寫方式,可是也帶來了很大的安全風險,所以這個 API 只能在安全的頁面中(好比部署了有效的 https 的頁面) 調用,而且必須是經過用戶的選擇進行操做(也就是不能徹底後臺自動執行)。數據庫
爲了讀取本地文件,咱們須要調用 window.chooseFileSystemEntries(option)
接口,它接受一個 option
做爲參數。數組
option
是一個對象,包含 4 個能夠配置的配置項:瀏覽器
配置項 | 取值 | 解釋 |
---|---|---|
type | 'openFile' /'saveFile' /'openDirectory' |
操做類型,是打開文件,保存文件仍是打開文件夾,默認是 'open-file' |
multiple | boolean |
容許用戶選擇單個文件仍是選擇多個文件,默認爲 false ,即只容許選擇單個文件 |
accepts | object[] |
指定彈出的文件選擇框中容許選擇的文件類型 |
accepts.description | string |
描述 |
accepts.mimeTypes | string[] |
容許選擇的文件的 MIME 類型 |
accepts.extensions | string[] |
容許選擇的文件的擴展名 |
excludeAcceptAllOption | boolean |
是否容許選擇不在上面 accepts 列表中容許的文件類型,默認爲 false ,即容許選擇任意文件(*.* ) |
window.chooseFileSystemEntries(option)
返回一個 FileSystemHandle
對象(若 option
中 multiple
被指定爲 true
,則返回一個 FileSystemHandle
數組)。安全
FileSystemHandle
是一個基類,實際上 chooseFileSystemEntries
根據 option
中指定的 type
,返回的是它的一個子類 FileSystemFileHandle
或 FileSystemDirectoryHandle
。異步
若指定的 option
是打開一個文件夾,則返回 FileSystemDirectoryHandle
,能夠進行目錄的遍歷,也能夠打開一個指定的文件,打開文件將返回一個 FileSystemFileHandle
。若指定的 option
是打開/保存一個文件,則直接返回 FileSystemFileHandle
。
經過 FileSystemFileHandle
上的 getFile()
函數,就能夠獲得一個 File
對象了。
這裏獲得 File
對象與 type="file"
的 input
返回的 File
同樣,你能夠經過經過經過 File
對象的 API 獲取文件的具體內容,好比對於文本文件,能夠直接調用 .text()
獲得字符串,對於二進制文件,能夠調用 .arrayBuffer()
獲得 ArrayBuffer
對象,也能夠調用 .stream()
獲得一個 ReadableStream
。
const handler = await window.chooseFileSystemEntries({
type: 'openFile',
accepts: [
{ description: '文本文件', extensions: ['txt'] },
],
});
const file = await handler.getFile();
const text = await file.text();
console.log(text);
複製代碼
爲了將文件保存到硬盤上,咱們須要調用 FileSystemFileHandle
對象上的 createWriter()
函數。
保存文件一般有兩種模式,一種是「保存」,即覆蓋原始文件,另外一種是「另存爲」,即建立一個新的文件。
「另存爲」的形式,咱們只須要從新調用 window.chooseFileSystemEntries()
提供保存文件的選項便可。而「保存」文件,咱們直接使用以前打開的文件的 FileSystemFileHandle
對象便可。
調用 createWriter()
函數後會返回一個 FileSystemWriter
對象,它包含三個函數:write(position, data)
、truncate(size)
和 close()
。
write
函數的第一個參數 position
爲一個數字,指示開始寫出的位置,第二個參數爲要寫出的數據,能夠是 ArrayBuffer
對象、ArrayBufferView
對象(Uint8Array
、DataView
之類的)、Blob
對象或者直接提供一個要寫出的字符串。
truncate
函數會將文件截斷爲指定長度。一般發生在要保存的文件比原始文件小,若是直接調用 write
函數,文件將只有前面一部分被改寫,然後面的原始文件內容仍是會殘留下來。
在調用 createWriter()
函數的時候,瀏覽器會先檢查用戶是否已經受權了文件寫出權限,若是以前是以「打開文件」或是「打開文件夾」的形式打開的文件,則默認是沒有文件寫出權限的,則此時瀏覽器會提示用戶以申請文件寫出權限。若是用戶拒絕了申請的權限,則將會拋出一個異常。
const handler = await window.chooseFileSystemEntries({
type: 'saveFile',
accepts: [
{ description: '文本文件', extensions: ['txt'] },
],
});
const writer = await handler.createWriter();
await writer.truncate(0); // 清空文件
await writer.write(0, 'Hello World!'); // 在文件頭寫入字符串
await writer.close();
const file = await handler.getFile();
const text = await file.text();
console.log(text);
複製代碼
在前面提到的 window.chooseFileSystemEntries()
函數中,option
能夠指定打開一個文件夾,此時瀏覽器會向用戶申請目錄遍歷權限,得到用戶贊成以後,就能夠獲得一個 FileSystemDirectoryHandle
對象。
FileSystemDirectoryHandle
對象上擁有一個 getEntries()
函數,它返回一個異步迭代器,能夠經過 for await ... of ...
循環對目錄進行遍歷。每次遍歷獲得的都是一個 FileSystemHandle
對象,能夠根據 .isFile
屬性與 .isDirectory
屬性判斷具體是 FileSystemFileHandle
對象仍是 FileSystemDirectoryHandle
對象。
除了使用 getEntries()
進行遍歷,還能夠經過 FileSystemDirectoryHandle
對象上的 getDirectory
函數打開一個指定的子目錄,子目錄一樣是一個 FileSystemDirectoryHandle
對象;也能夠經過 getFile
函數打開一個指定的文件,獲得 FileSystemFileHandle
對象。
const handler = await window.chooseFileSystemEntries({
type: 'openDirectory',
});
const ls = async (path, handler) => {
const result = [];
for await (const h of await handler.getEntries()) {
if (h.isFile) {
result.push(`${path}/${h.name}`);
} else if (h.isDirectory) {
const subDirectory = await ls(`${path}/${h.name}`, h);
result.push(...subDirectory);
}
}
return result;
};
const files = await ls(handler.name, handler);
console.log(files);
複製代碼
這個新的 Native File System API 的全部 API 均以 Promise
的形式進行返回,這使得瀏覽器能夠在函數返回以前進行一切權限的申請與檢查而不會阻塞其餘代碼的執行。在權限不足的時候,瀏覽器能夠在 Promise
等待的階段向用戶申請權限。
爲了不惡意網站濫用這個 API 向用戶電腦寫入惡意文件,window.chooseFileSystemEntries()
函數的調用必須是在安全的頁面中,由用戶主動觸發的,也就是必須放在用戶交互的回調中(好比按鈕的點擊事件),這與 type="file"
的 input
相似。
瀏覽器可能會處於保護的目的對一些與操做系統相關的文件夾進行限制訪問。而且在網站嘗試向本地文件夾寫入文件的時候,瀏覽器會給出通知。
爲了不頻繁地彈出權限警告,瀏覽器將會保存用戶授予的權限,一旦用戶授予網頁訪問某一文件,網頁在被關閉以前都將擁有這個權限而不會過時。
一旦網頁被關閉,全部的權限都將被回收,用戶在下一次打開相同網站時,網站須要從新申請權限。
將來,網頁能夠將受權信息存入網頁內置的數據庫(IndexedDB)中,以持久化權限請求。
到目前爲止,這個 API 尚未開發完成,標準任然可能被修改或刪除。可是這個 API 的出現無疑爲 WebIDE 的開發提供了可能,將來可能能夠再也不須要安裝本地 IDE,直接在網頁中打開本地的工程目錄,對代碼進行編輯、保存,並直接使用線上虛擬化環境進行代碼的執行測試。
可是,這個 API 的出現是否會帶來嚴重的安全問題,如今還不得而知,畢竟對於廣大小白用戶來講,即使網站給出了安全警告,他們依舊會直接忽略。不是他們不重視,是他們根本看不懂。
好比即使是網站 HTTPS 配置錯誤,展現出了可能存在風險的警告,用戶仍是會習慣性的選擇「高級」-「繼續進入」;因此出現了 HSTS,在網站出現 HTTPS 錯誤時,禁止用戶繼續使用網站。
如此可見,即使是瀏覽器明確提示你存在安全問題,用戶仍是選擇性忽略,因此對於 Native File System API 給出的權限申請,展現的只是個「提示框」,對於廣大普通用戶來講根本「不會放在眼裏」,直接無腦肯定就是了。
若用戶訪問到惡意網站,一個權限申請,一旦點擊了「肯定」,整個硬盤的數據都被加密……
安全,與便捷,仍是須要一個平衡點!
emmmm……是否是跑題了。。。
附:標準文檔:wicg.github.io/native-file…
一個 Chrome 實驗室提供的純文本編輯器 Demo:googlechromelabs.github.io/text-editor…