文件系统大师之路:从入门到精通 File System Access API

第一章:革新 Web 应用 - File System Access API 简介

本章旨在阐明文件处理在 Web 发展历史中的背景和局限性,并将文件系统访问 API(File System Access API)定位为一项革命性的技术。

1.1 超越沙箱:为何传统方式(<input type="file">)已然不足

数十年来,浏览器的安全模型一直建立在严格的“沙箱”原则之上,旨在防止网页直接与用户的本地文件系统进行交互。这是一个至关重要的安全特性,但同时也极大地限制了构建功能强大的、桌面级应用程序的能力 1。

传统的网页文件处理方式主要依赖于 <input type="file"> 元素和 <a download> 标签。当用户通过 <input type="file"> 选择一个文件时,Web 应用获得的是一个只读的 File 对象,其中包含了文件的内容和一些元数据,但这个对象与磁盘上的原始文件之间没有持久的联系 3。如果应用需要“保存”文件,它只能创建一个新的文件副本,然后通过 

<a download> 属性触发浏览器下载这个副本。这种模式无法实现对原始文件的就地修改 3。

这种工作流程形成了一种笨拙的“上传 -> 处理 -> 下载副本”的循环。这种模式迫使 Web 应用只能成为本地文件的消费者,而无法成为真正的编辑器。其核心问题在于:

  1. 用户通过 <input type="file"> 选择文件。

  2. 浏览器在内存中创建一个包含文件数据的 File 对象(一个数据“快照”)3。

  3. 应用程序可以读取这份数据,但它完全不知道该文件在磁盘上的原始位置。

  4. 当用户点击“保存”时,应用程序实际上是创建了一个新的数据块(Blob),并通过 <a download> 标签让用户下载这个新文件。

  5. 最终,用户得到了两个文件:原始文件和新下载的修改后版本。这在桌面应用中相当于每次都执行“另存为”,而不是真正的“保存”。这种糟糕的用户体验严重阻碍了 Web 应用与原生应用(如文本编辑器、IDE)在功能上竞争。

1.2 迎接 File System Access API:浏览器中的真正文件 I/O

File System Access API 是一项现代 Web 标准,旨在安全地打破旧有的沙箱模型,允许 Web 应用在获得用户明确授权后,直接读取、写入和管理用户本地设备上的文件和目录 2。它的核心目标是为 Web 平台带来构建专业级应用(如 IDE、文本编辑器、图像和视频编辑器)所必需的文件系统交互能力 2。

这项新 API 与传统方法相比,在功能和用户体验上都实现了质的飞跃。下表清晰地总结了二者之间的关键差异,直观地展示了学习这项新 API 的价值所在。

表 1:File System Access API 与传统方法的对比

功能传统方法 (<input><a download>)File System Access API
读取文件用户选择文件,应用获得一个临时的内存副本。用户选择文件,应用获得一个持久的文件句柄。
保存文件只能下载一个新副本,无法覆盖原始文件。能够覆盖原始文件,实现真正的“保存”操作。
目录访问依赖非标准的 webkitdirectory 属性,功能有限。提供标准 API,可遍历、创建和删除文件及子目录。
用户体验“上传/下载”模式,页面刷新后状态丢失。“打开/保存”模式,状态可以被持久化。
权限模型隐式授予单次读取权限。显式的、粒度化的读/写权限控制。

1.3 核心概念:句柄、用户手势与安全承诺

要理解 File System Access API,必须掌握其几个核心概念。

首先是句柄(Handles)。API 的核心抽象是 FileSystemFileHandle(文件句柄)和 FileSystemDirectoryHandle(目录句柄)。它们代表了文件系统中的一个文件或目录的引用或“指针”,而不是文件内容本身 6。这个句柄是轻量级的,并且可以被序列化,这意味着它可以被长期保存,从而实现持久化访问。

其次是安全上下文(Secure Context)用户手势(User Gesture)。由于该 API 的强大能力,浏览器实施了严格的安全措施。所有能够触发文件或目录选择器的 API 调用(如 window.showOpenFilePicker())都必须在安全上下文(即 HTTPS)中执行,并且必须由用户的主动交互(如点击按钮)触发 2。这意味着网页无法在后台静默地访问用户文件,用户始终掌握着控制权。

与传统 File 对象不同,FileSystemFileHandle 是一个指向磁盘上某个位置的动态指针。传统的 File 对象一旦被读入内存,就与磁盘上的原始文件失去了联系;如果原始文件被其他程序修改,内存中的 File 对象不会有任何变化。而文件句柄则是一个“实时”链接。每次调用 handle.getFile() 方法时,它都会从磁盘上读取文件的当前状态 8。这意味着如果用户使用其他程序修改了文件,Web 应用下一次通过句柄读取时将能获取到最新的内容。这种设计对于需要与其他工具协同工作的应用至关重要。

1.4 双系统传说:用户可见文件系统与源私有文件系统(OPFS)

File System Access API 能够与两种截然不同的文件系统进行交互。第一种是用户可见的常规文件系统,例如用户的“文档”或“下载”文件夹。第二种是源私有文件系统(Origin Private File System, OPFS) 1。

OPFS 是一个为每个网站源(origin)独立提供的、与操作系统文件系统隔离的沙箱化存储空间。它对用户不可见,但为 Web 应用提供了一个高性能的文件存储后端。OPFS 中的文件操作经过高度优化,尤其适合需要频繁、快速读写的场景,例如 WebAssembly 应用、数据库引擎或大型游戏资源的管理 1。我们将在第四章深入探讨 OPFS 的高级应用。

第二章:读者的工具箱 - 访问本地文件和目录

本章将提供详尽的实践代码,指导您开始读取本地文件和目录。

2.1 开启门户:使用 window.showOpenFilePicker() 读取单个和多个文件

读取文件的入口点是 window.showOpenFilePicker() 方法。调用它会向用户显示一个标准的文件选择对话框 2。该方法返回一个 Promise,成功解析后会得到一个包含文件句柄的数组。

读取单个文件:

JavaScript

let fileHandle;
const button = document.getElementById('open-file-btn');

button.addEventListener('click', async () => {
  try {
    // showOpenFilePicker 返回一个数组,即使只选择一个文件
    // 使用数组解构可以方便地获取第一个句柄
    [fileHandle] = await window.showOpenFilePicker();
    console.log(fileHandle);
  } catch (err) {
    if (err.name === 'AbortError') {
      // 用户取消了文件选择,这不是一个真正的错误
      console.log('User cancelled the file picker.');
    } else {
      console.error(err);
    }
  }
});

自定义文件选择器并读取多个文件:

可以通过传递一个 options 对象来自定义文件选择器的行为。例如,可以限制可选的文件类型或允许选择多个文件 2。

JavaScript

const openImagesButton = document.getElementById('open-images-btn');

openImagesButton.addEventListener('click', async () => {
  const pickerOpts = {
    // 允许选择多种类型的文件
    types: [
      {
        description: 'Images',
        accept: {
          'image/jpeg': ['.jpg', '.jpeg'],
          'image/png': ['.png'],
        },
      },
      {
        description: 'Text',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
    // 不显示“所有文件”选项
    excludeAcceptAllOption: true,
    // 允许用户选择多个文件
    multiple: true,
  };

  try {
    const fileHandles = await window.showOpenFilePicker(pickerOpts);
    for (const handle of fileHandles) {
      console.log(`Selected file: ${handle.name}`);
    }
  } catch (err) {
    console.error(err);
  }
});

2.2 理解 FileSystemFileHandle:通往文件的钥匙

FileSystemFileHandle 是您与文件交互的凭证。它有两个核心属性 2:

  • kind: 一个字符串,对于文件句柄,其值为 'file'

  • name: 文件的名称,例如 'document.txt'

需要再次强调,句柄本身不是文件内容。它只是一个引用,一个通往文件的“钥匙”。

2.3 从句柄到内容:使用 getFile() 读取数据

获得了文件句柄后,下一步就是读取文件内容。这需要两步完成:

  1. 调用 fileHandle.getFile() 方法。这个异步方法会返回一个 Promise,解析后得到一个标准的 File 对象。这个 File 对象包含了文件的快照数据 8。

  2. 使用 File 对象上的标准方法(如 file.text()file.arrayBuffer() 或 file.stream())来读取具体内容 2。

这种设计将 API 的新部分(句柄)与开发者已经熟悉的部分(File API)巧妙地连接起来,降低了学习曲线。

完整示例:选择并显示文本文件内容

JavaScript

const openBtn = document.getElementById('open-btn');
const contentEl = document.getElementById('content');

openBtn.addEventListener('click', async () => {
  try {
    const [fileHandle] = await window.showOpenFilePicker();
    
    // 1. 从句柄获取 File 对象
    const file = await fileHandle.getFile();
    
    // 2. 读取文件内容为文本
    const contents = await file.text();
    
    // 3. 将内容显示在页面上
    contentEl.textContent = contents;
  } catch (err) {
    console.error(err);
  }
});

2.4 探索迷宫:使用 window.showDirectoryPicker() 浏览目录结构

除了文件,该 API 还提供了强大的目录访问能力。window.showDirectoryPicker() 方法是访问目录的入口点,它会提示用户选择一个文件夹 6。

调用此方法会返回一个 FileSystemDirectoryHandle。如果希望对目录进行写入操作(例如创建或删除文件),可以在调用时请求写权限 12:

JavaScript

const openDirBtn = document.getElementById('open-dir-btn');

openDirBtn.addEventListener('click', async () => {
  try {
    // 请求读写权限
    const dirHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
    console.log('Directory handle:', dirHandle);
  } catch (err) {
    console.error(err);
  }
});

2.5 掌握异步迭代:使用 for await...of 遍历目录条目

FileSystemDirectoryHandle 提供了一组异步迭代器方法,用于遍历目录内容,包括 entries()keys() 和 values() 14。处理这些异步迭代器的最佳方式是使用 ES2018 引入的 

for await...of 循环语法。

这种现代语法不仅代码可读性极高,而且完美契合了 API 的设计理念。由于目录可能包含成千上万个文件,一次性加载所有条目会非常低效。异步迭代器允许我们按需、逐个地处理条目,而 for await...of 则优雅地处理了这一过程中的 Promise 解析 16。学习使用此 API 的过程,本身也是一次对现代 JavaScript 异步编程范式的实践。

示例:递归遍历目录并打印所有文件名

以下函数展示了如何递归地遍历一个目录及其所有子目录,并打印出每个文件的相对路径。

JavaScript

async function listAllFilesAndDirs(dirHandle, path = dirHandle.name) {
  // dirHandle.values() 返回一个异步迭代器
  for await (const entry of dirHandle.values()) {
    const newPath = `${path}/${entry.name}`;
    console.log(newPath);
    if (entry.kind === 'directory') {
      await listAllFilesAndDirs(entry, newPath);
    }
  }
}

// 假设 dirHandle 是通过 showDirectoryPicker 获取的
// listAllFilesAndDirs(dirHandle);

注意:有报告指出,在处理包含数千个文件的非常大的目录时,异步迭代器可能会出现不一致的结果,并非每次都能返回完整的条目列表。开发者在使用时应意识到这一潜在的局限性 19。

第三章:创造者的工坊 - 写入、保存与修改文件

本章将全面介绍 API 的“写”操作,让您能够构建可以创建和修改数据的应用程序。

3.1 从白板到存档:window.showSaveFilePicker() 工作流

当需要创建一个新文件并保存时,应使用 window.showSaveFilePicker() 方法。它会弹出一个文件保存对话框,让用户选择保存位置和文件名 5。

与 showOpenFilePicker 类似,showSaveFilePicker 也接受一个 options 对象,其中 suggestedName 和 types 属性可以极大地改善用户体验 7。

示例:将文本框内容保存为新文件

JavaScript

const saveAsBtn = document.getElementById('save-as-btn');
const textArea = document.getElementById('text-editor');

saveAsBtn.addEventListener('click', async () => {
  try {
    const saveOptions = {
      suggestedName: 'untitled.txt',
      types: },
      }],
    };
    
    const fileHandle = await window.showSaveFilePicker(saveOptions);
    // 接下来是写入操作...
  } catch (err) {
    console.error(err);
  }
});

3.2 写入操作剖析:createWritable()write() 与至关重要的 close()

写入文件的过程分为三个关键步骤,缺一不可:

  1. 创建可写流 (createWritable): 首先,在文件句柄上调用 handle.createWritable() 方法。这个异步方法会返回一个 FileSystemWritableFileStream 对象 20。这是一个关键步骤,因为浏览器通常会在此阶段创建一个临时文件(或称“交换文件”),所有的写入操作都将先作用于这个临时文件。

  2. 写入内容 (write): 接着,调用可写流的 writable.write() 方法,将数据写入流中。write 方法可以接受字符串、Buffer 或 Blob 作为参数 5。

  3. 关闭流 (close): 最后,也是最重要的一步,调用 writable.close() 方法。这个方法会结束写入过程,并将临时文件中的内容原子性地应用到目标文件中(通常是通过重命名或移动操作)5。

忘记调用 close() 是一个常见的初学者错误,这会导致文件内容不会被真正保存。

这种“写入临时文件,关闭时替换”的模式是一个内置的安全机制。它确保了文件操作的原子性。如果写入过程中浏览器崩溃或断电,原始文件将保持不变,避免了文件被损坏或处于“半写入”状态。只有当 close() 成功执行时,文件才会被完整、正确地更新。这个事务性的特性为数据完整性提供了强大的保障,而开发者无需手动实现 20。

完整写入函数示例:

JavaScript

async function saveFile(fileHandle, contents) {
  // 1. 创建可写文件流
  const writable = await fileHandle.createWritable();
  
  // 2. 写入内容
  await writable.write(contents);
  
  // 3. 关闭文件流并写入磁盘
  await writable.close();
  
  console.log('File saved successfully!');
}

3.3 就地编辑:精确修改现有文件

File System Access API 最大的优势之一就是能够实现真正的“保存”操作,即就地修改文件。这结合了第二章的读取和本章的写入概念。

完整的“打开 -> 编辑 -> 保存”周期示例:

JavaScript

let currentFileHandle;
const openBtn = document.getElementById('open-btn');
const saveBtn = document.getElementById('save-btn');
const editor = document.getElementById('editor');

// 打开文件
openBtn.addEventListener('click', async () => {
  [currentFileHandle] = await window.showOpenFilePicker();
  const file = await currentFileHandle.getFile();
  editor.value = await file.text();
  saveBtn.disabled = false;
});

// 保存文件
saveBtn.addEventListener('click', async () => {
  if (!currentFileHandle) return;
  
  const writable = await currentFileHandle.createWritable();
  await writable.write(editor.value);
  await writable.close();
  alert('File saved!');
});

在调用 createWritable 时,可以传入 keepExistingData: true 选项,这在某些需要保留现有文件内容并进行修改的场景中非常有用 20。

3.4 从零构建:以编程方式创建新文件和目录

API 允许在用户选择的目录中以编程方式创建新的文件和子目录。这通过在 get...Handle 方法中传递 { create: true } 选项来实现 22。

示例:在所选目录中创建一个新文件和一个新文件夹

JavaScript

async function createStructure(dirHandle) {
  // 创建一个新文件夹 'logs'
  const logsDirHandle = await dirHandle.getDirectoryHandle('logs', { create: true });
  
  // 在 'logs' 文件夹中创建一个新文件 'today.log'
  const logFileHandle = await logsDirHandle.getFileHandle('today.log', { create: true });
  
  // 向新文件中写入内容
  const writable = await logFileHandle.createWritable();
  await writable.write(`Log entry at ${new Date().toISOString()}`);
  await writable.close();
  
  console.log('Created logs/today.log');
}

值得注意的是,API 的设计是层级化和显式的。您不能通过一次调用 dirHandle.getFileHandle('subdir/myfile.txt', { create: true }) 来创建跨层级的文件。API 规定 name 参数必须是有效的文件名,而不是路径 26。您必须首先获取或创建 

subdir 的目录句柄,然后才能在该句柄上创建 myfile.txt 27。这种设计强制开发者进行明确的、分步的创建过程,增强了操作的透明度和安全性,避免了单次调用可能引发的复杂递归创建行为。

3.5 清理工作:删除文件和文件夹

要在目录中删除条目,可以使用 directoryHandle.removeEntry(name) 方法。如果要删除一个非空目录,需要设置 { recursive: true } 选项 28。

JavaScript

// 删除名为 'old-file.txt' 的文件
// 假设 dirHandle 是一个有效的目录句柄
await dirHandle.removeEntry('old-file.txt');

// 递归删除名为 'temp-folder' 的目录及其所有内容
await dirHandle.removeEntry('temp-folder', { recursive: true });

此外,一个更新的、更符合人体工程学的方法是直接在文件或目录句柄本身上调用 handle.remove()。这个方法允许通过条目自身的句柄来删除它,而无需先获取其父目录的句柄。不过,需要注意该方法的浏览器支持度相对有限 29。

第四章:构建健壮应用的高级技巧

本章将引导您从基础用法过渡到构建专业、有状态的应用程序。

4.1 权限模型深度解析:queryPermission() 与 requestPermission()

File System Access API 的安全模型围绕着一个动态的、以用户为中心的权限系统构建。权限状态共有三种:'granted'(已授予)、'denied'(已拒绝)和 'prompt'(需要询问)31。

  • queryPermission(): 这个方法用于查询句柄的当前权限状态,而不会打扰用户。它接受一个可选的 { mode: 'readwrite' } 参数来查询读写权限 31。这是一个被动的检查。

  • requestPermission(): 当权限状态为 'prompt' 时,您必须调用此方法来请求用户授权。这个调用会触发一个浏览器级别的权限提示框,并且必须由用户手势激活 33。

权限并非永久性的。浏览器可能会在会话结束后重置权限,用户也可以随时在浏览器设置中撤销权限 7。

这种权限的生命周期与会话状态和用户交互紧密相关。当一个应用通过 IndexedDB 等方式持久化了一个文件句柄后,下次启动时,浏览器出于安全考虑,通常会将该句柄的权限状态重置为 'prompt' 31。因此,一个健壮的应用在对持久化的句柄进行任何操作前,都必须遵循“先查询,后请求”的模式。

推荐的权限验证流程:

JavaScript

async function verifyPermission(fileHandle, withWrite = false) {
  const options = { mode: withWrite? 'readwrite' : 'read' };
  
  // 查询当前权限状态
  if (await fileHandle.queryPermission(options) === 'granted') {
    return true;
  }
  
  // 如果未授予,则请求权限
  if (await fileHandle.requestPermission(options) === 'granted') {
    return true;
  }
  
  // 用户拒绝了权限
  return false;
}

在尝试读写之前调用此函数,可以确保应用总是在拥有必要权限的情况下运行,同时为用户提供了清晰的控制权。

4.2 构建持久化体验:在 IndexedDB 中存储文件和目录句柄

FileSystemHandle 对象是可序列化的,这意味着它们可以被存储在 IndexedDB 中 7。这为构建能够“记住”用户工作状态的应用打开了大门,例如实现“最近打开的文件”列表或在应用启动时自动重新打开上次编辑的目录。

以下是使用一个简单的库 idb-keyval 来存储和检索文件句柄的示例 35:

JavaScript

import { get, set } from 'idb-keyval';

const RECENT_FILE_KEY = 'recent-file-handle';

// 保存句柄
async function saveHandle(fileHandle) {
  await set(RECENT_FILE_KEY, fileHandle);
}

// 检索句柄
async function getHandle() {
  return await get(RECENT_FILE_KEY);
}

// 应用启动逻辑
async function onAppLoad() {
  const recentFileHandle = await getHandle();
  if (recentFileHandle) {
    // 重新加载文件前,必须验证权限!
    const hasPermission = await verifyPermission(recentFileHandle, true);
    if (hasPermission) {
      // 加载文件内容...
    } else {
      alert('Could not get permission for the last opened file.');
    }
  }
}

这个例子与上一节的权限模型紧密相连,展示了从 IndexedDB 检索句柄后,必须重新验证权限的完整流程。

4.3 高性能存储:源私有文件系统(OPFS)入门

OPFS 是为需要极致性能的场景设计的。通过 navigator.storage.getDirectory() 可以获取 OPFS 的根目录句柄 6。

OPFS 的最大优势在于,它允许在 Web Worker 中通过 fileHandle.createSyncAccessHandle() 方法进行同步文件 I/O 操作 1。在常规的 Web 开发中,同步操作通常会阻塞主线程,但 Web Worker 的独立线程环境使其成为可能。对于需要处理大量二进制数据、对性能要求极高的应用(如运行在 WebAssembly 上的 SQLite 数据库、视频编辑器或大型游戏),同步 API 能够避免 Promise 带来的开销,从而实现接近原生的性能。

第五章:从开发到部署 - 生产级最佳实践

本章将为您提供构建可靠、生产质量应用的知识。

5.1 防御性编程:常见错误及其优雅处理

与文件系统交互时,错误在所难免。使用 try...catch 块捕获异常,并通过检查 error.name 属性来识别错误类型,是提供良好用户体验的关键 36。

常见的 DOMException 错误包括 9:

  • AbortError: 用户主动关闭了文件选择或保存对话框。这通常不应被视为错误,可以静默处理。

  • NotAllowedError: 用户拒绝了权限请求。应用应向用户解释为何需要权限,并提供替代方案。

  • NotFoundError: 尝试访问的文件或目录不存在。

  • TypeMismatchError: 尝试将一个目录作为文件打开,或反之。

  • InvalidModificationError: 进行了不允许的修改,例如尝试删除一个非空目录但未设置 recursive: true

错误处理示例:

JavaScript

async function saveMyFile() {
  try {
    const handle = await window.showSaveFilePicker();
    //... 写入操作
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('Save operation was cancelled by the user.');
    } else {
      alert(`An error occurred: ${err.name} - ${err.message}`);
    }
  }
}

5.2 确保通用访问:浏览器兼容性与优雅降级

File System Access API 是一项现代技术,并非所有浏览器都完全支持。因此,为保证应用的普适性,必须考虑浏览器兼容性并提供优雅降级方案。

表 2:浏览器兼容性矩阵

下表总结了 API 核心功能在主流桌面浏览器中的支持情况 7。

功能ChromeFirefoxSafariEdge
showOpenFilePicker86+111+15.2+86+
showSaveFilePicker86+15.2+86+
showDirectoryPicker86+111+15.2+86+
createWritable86+111+15.2+86+
OPFS (getDirectory)86+111+15.2+86+
createSyncAccessHandle102+111+15.2+102+

优雅降级(Graceful Fallback)

优雅降级是一种设计策略,即在现代浏览器中提供最佳体验,同时在不支持新特性的旧浏览器中提供基础的核心功能 41。对于 File System Access API,这意味着:

  1. 特性检测: 在调用 API 之前,先检查它是否存在。

  2. 提供备用方案: 如果 API 不存在,则回退到传统的文件处理方式。

示例:一个同时支持新旧浏览器的保存函数

JavaScript

async function saveContent(blob, fileName) {
  // 1. 特性检测
  if ('showSaveFilePicker' in window) {
    try {
      // 使用新 API
      const handle = await window.showSaveFilePicker({ suggestedName: fileName });
      const writable = await handle.createWritable();
      await writable.write(blob);
      await writable.close();
      return;
    } catch (err) {
      if (err.name === 'AbortError') return;
      console.error(err);
    }
  }
  
  // 2. 优雅降级
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = fileName;
  a.style.display = 'none';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(a.href);
}

为了简化这一过程,可以考虑使用 browser-fs-access 这样的库,它封装了特性检测和优雅降级的逻辑,让开发者能用统一的接口编写代码 2。

5.3 融会贯通:从零开始构建一个迷你文本编辑器

作为本指南的总结,我们将构建一个简单的文本编辑器,它将综合运用前面学到的所有核心概念:

  1. UI: 包含“打开”、“保存”、“另存为”按钮和一个 <textarea> 编辑区。

  2. 状态管理: 使用一个变量来存储当前文件的 fileHandle

  3. 文件操作:

    • “打开”按钮使用 showOpenFilePicker,读取文件内容并填充到文本区。

    • “保存”按钮重用已存储的 fileHandle,并使用 createWritable 将文本区内容写回文件。

    • “另存为”按钮使用 showSaveFilePicker 将内容保存到新文件。

  4. 权限处理: 在每次保存操作前,都使用 verifyPermission 辅助函数来检查和请求写权限。

  5. 持久化: 使用 IndexedDB 存储最后一个打开文件的句柄,并在页面加载时尝试重新打开它。

  6. 兼容性: 对所有文件操作进行特性检测,为不支持 API 的浏览器提供基于 <input> 和 <a download> 的降级方案。

这个项目将把所有抽象的知识点转化为一个具体、可用的应用程序,为您掌握 File System Access API 打下坚实的基础。

结论:未来在你手中

File System Access API 是一项真正具有变革意义的技术。它打破了长期以来束缚 Web 应用的枷锁,极大地缩小了 Web 应用与原生桌面应用之间的功能差距。通过提供对本地文件系统的直接、安全且持久的访问能力,它为开发者创造前所未有的、功能丰富的在线体验铺平了道路。

从构建一个简单的文本编辑器到开发复杂的、基于云和本地混合存储的专业工具,这项 API 开启了无限可能。掌握它,意味着您不仅学会了一项新技术,更是获得了一把开启下一代 Web 应用大门的钥匙。现在,是时候去探索和创造了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值