首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >浏览器能直接写文件了!一文深入 File System Access API 的工作原理与实战应用

浏览器能直接写文件了!一文深入 File System Access API 的工作原理与实战应用

作者头像
前端达人
发布2026-03-12 14:35:42
发布2026-03-12 14:35:42
60
举报
文章被收录于专栏:前端达人前端达人
image
image

你是否想过,为什么在线编辑器(如 Google Docs)能让你存储文件,但 VS Code Web 版本却始终受到沙箱限制?答案就在 File System Access API 这个"破局者"身上。

这个 API 不仅打破了浏览器 30 年来的沙箱隔离,还通过精妙的权限设计在"能力"与"安全"之间找到了平衡。今天我们就从原理到实战,彻底搞透它怎么工作的。

浏览器沙箱的困局:为什么 Web 应用需要这个 API?

说起浏览器沙箱,它就像一个"监狱"——保护系统安全,但也限制了 Web 应用的能力。想象你有一个在线代码编辑器,用户辛苦写好了 50kb 的代码文件,关掉浏览器一切就烟消云散。或者你想做一个在线设计工具,每次都得从云端同步最新的 PSD 文件……这些都是 Web 应用的痛点。

传统方案很无奈:

  • FileReader API:只能读,不能写;用户每次都得重新选择文件
  • a 标签下载:只能把浏览器数据导出,无法直接改动本地文件
  • WebRTC + 服务器中转:需要上传到云端再下载,浪费带宽和时间

File System Access API 的出现改变了这一切。它不仅让 Web 应用获得本地文件读写权限,还通过"用户主动授权"这个机制,在开放能力的同时守住了安全底线。

这种设计思路值得学习:权限来自于用户的每一次主动选择,而不是默认赋予

核心概念:从文件选择器到句柄的魔法转换

在深入代码之前,你需要理解一个核心概念:FileSystemHandle(文件系统句柄)

这是 API 的灵魂。句柄就像一把"智能钥匙",它代表了用户授予的对某个特定文件或目录的访问权限。有了这把钥匙,你可以:

代码语言:javascript
复制
用户选择文件 → 获得句柄 → 读取/修改/删除 → 写回磁盘
   ↓ 权限授予    ↓ 有效期内    ↓ 细粒度操作    ↓ 持久化

让我们用伪代码描述这个流程:

代码语言:javascript
复制
┌─────────────────────────────────────────────────────────┐
│          File System Access API 的工作流                │
└─────────────────────────────────────────────────────────┘

用户点击"打开文件"按钮
           ↓
   显示系统文件选择器
           ↓
 用户选择文件 (赋予权限)
           ↓
获得 FileSystemFileHandle 句柄
           ↓
    ┌─────┬──────┬──────────┐
    ↓     ↓      ↓          ↓
  读取  写入   删除      遍历属性
    └─────┴──────┴──────────┘
           ↓
    对本地文件操作

关键点是:权限是临时的还是持久的?

在最新的规范中,浏览器可以通过 IndexedDB 存储权限信息,实现"记住我选过的文件夹"这样的功能。但这需要用户明确授权,不会在后台偷偷记录。

深入代码:打开文件的三种姿势

方式 1:单文件选择(最常见)

代码语言:javascript
复制
// TypeScript 版本
asyncfunction openSingleFile(): Promise<string | null> {
try {
    // showOpenFilePicker 返回一个 FileSystemFileHandle 数组
    const [fileHandle] = awaitwindow.showOpenFilePicker({
      types: [
        {
          description: 'Text Files',
          accept: { 'text/plain': ['.txt', '.md'] }
        }
      ],
      multiple: false// 只选择一个文件
    });

    // 获取 File 对象(标准 Web API)
    const file = await fileHandle.getFile();
    
    // 读取文件内容
    const content = await file.text();
    
    return content;
  } catch (err) {
    // 用户取消选择
    if (err instanceof DOMException && err.name === 'AbortError') {
      console.log('用户取消了文件选择');
    }
    returnnull;
  }
}

// JavaScript 版本
asyncfunction openSingleFile() {
try {
    const [fileHandle] = awaitwindow.showOpenFilePicker({
      types: [
        {
          description: 'Text Files',
          accept: { 'text/plain': ['.txt', '.md'] }
        }
      ],
      multiple: false
    });

    const file = await fileHandle.getFile();
    const content = await file.text();
    return content;
  } catch (err) {
    if (err instanceof DOMException && err.name === 'AbortError') {
      console.log('用户取消了文件选择');
    }
    returnnull;
  }
}

关键细节解析

  • showOpenFilePicker() 是异步的,UI 线程不会被阻塞
  • types 参数用 MIME 类型和扩展名双重过滤(体验更好)
  • fileHandle.getFile() 返回标准的 File 对象,可用所有现有方法(.text().arrayBuffer() 等)

方式 2:多文件选择与文件元信息

如果你要做一个批量处理工具(比如批量压缩图片),需要同时处理多个文件:

代码语言:javascript
复制
interface FileInfo {
  name: string;
  size: number;
type: string;
  lastModified: number;
  handle: FileSystemFileHandle;
}

asyncfunction openMultipleFiles(): Promise<FileInfo[]> {
try {
    // 注意:multiple: true
    const fileHandles = awaitwindow.showOpenFilePicker({
      types: [
        {
          description: 'Images',
          accept: { 'image/*': ['.png', '.jpg', '.webp'] }
        }
      ],
      multiple: true
    });

    const files: FileInfo[] = [];

    for (const handle of fileHandles) {
      const file = await handle.getFile();
      
      files.push({
        name: file.name,
        size: file.size,
        type: file.type,
        lastModified: file.lastModified,
        handle: handle
      });
    }

    return files;
  } catch (err) {
    console.error('打开文件失败:', err);
    return [];
  }
}

// 使用示例
asyncfunction processImages() {
const images = await openMultipleFiles();

// 显示进度
for (const img of images) {
    console.log(`处理中: ${img.name} (${(img.size / 1024).toFixed(2)}KB)`);
    
    // 这里可以读取图片、压缩、处理等
    const imageData = await img.handle.getFile().then(f => f.arrayBuffer());
    // ... 处理逻辑
  }
}

生产级别的建议

在中国常见的场景中,比如某个 AI 图片处理平台,用户可能一次上传 100+ 图片。此时应该:

  1. 前端验证:检查文件数量、单个大小
  2. 增量处理:不要一次性全部加载到内存
  3. 进度反馈:用户需要知道处理进展
  4. 错误恢复:单个文件失败不应该中断整个流程

文件写入的"赋能"与"制约"

现在到了有趣的部分:写文件。这是 Web 应用实现"编辑→保存"闭环的关键。

核心概念:Writable Stream(可写流)

写入文件不是一次性操作,而是通过流式接口实现的:

代码语言:javascript
复制
async function saveFile(content: string, suggestedName: string = 'document.txt'): Promise<boolean> {
try {
    // showSaveFilePicker 让用户选择保存位置
    const fileHandle = awaitwindow.showSaveFilePicker({
      suggestedName,
      types: [
        {
          description: 'Text Files',
          accept: { 'text/plain': ['.txt'] }
        }
      ]
    });

    // 关键!获取可写流
    const writable = await fileHandle.createWritable();

    // 写入内容
    // write() 方法接受字符串、Blob、ArrayBuffer 等
    await writable.write(content);

    // 务必关闭流,否则文件不会真正保存到磁盘
    await writable.close();

    console.log('✅ 文件已保存');
    returntrue;
  } catch (err) {
    if (err instanceof DOMException && err.name === 'AbortError') {
      console.log('❌ 用户取消了保存');
    } else {
      console.error('保存失败:', err);
    }
    returnfalse;
  }
}

// JavaScript 版本
asyncfunction saveFile(content, suggestedName = 'document.txt') {
try {
    const fileHandle = awaitwindow.showSaveFilePicker({
      suggestedName,
      types: [
        {
          description: 'Text Files',
          accept: { 'text/plain': ['.txt'] }
        }
      ]
    });

    const writable = await fileHandle.createWritable();
    await writable.write(content);
    await writable.close();

    console.log('✅ 文件已保存');
    returntrue;
  } catch (err) {
    if (err instanceof DOMException && err.name === 'AbortError') {
      console.log('❌ 用户取消了保存');
    } else {
      console.error('保存失败:', err);
    }
    returnfalse;
  }
}

坑点警告

  1. 必须 close():很多开发者忘记关闭流,导致文件没有真正写入。这不会报错,但文件是空的。
  2. 覆盖 vs 追加write() 默认从头覆盖,如果要追加内容:
代码语言:javascript
复制
async function appendToFile(fileHandle: FileSystemFileHandle, newContent: string) {
const writable = await fileHandle.createWritable();

// 获取文件大小,移动到末尾
const file = await fileHandle.getFile();
await writable.seek(file.size);

// 追加内容
await writable.write(newContent);
await writable.close();
}

目录操作:构建本地文件浏览器

如果你要做一个"本地文件管理器",需要操作整个目录。API 提供了丰富的目录操作能力:

递归读取目录结构

代码语言:javascript
复制
interface FileTreeNode {
  name: string;
  kind: 'file' | 'directory';
  handle: FileSystemHandle;
  children?: FileTreeNode[];
  size?: number; // 仅文件有
  modifiedTime?: number; // 仅文件有
}

asyncfunction buildFileTree(
  directoryHandle: FileSystemDirectoryHandle,
  depth: number = 0,
  maxDepth: number = 5
): Promise<FileTreeNode[]> {
// 防止无限递归
if (depth > maxDepth) return [];

const tree: FileTreeNode[] = [];

try {
    // 遍历目录内容
    forawait (const entry of directoryHandle.entries()) {
      const [name, handle] = entry;

      const node: FileTreeNode = {
        name,
        kind: handle.kind,
        handle
      };

      // 如果是文件,获取元信息
      if (handle.kind === 'file') {
        const file = await handle.getFile();
        node.size = file.size;
        node.modifiedTime = file.lastModified;
      }
      // 如果是目录,递归遍历
      elseif (handle.kind === 'directory') {
        node.children = await buildFileTree(
          handle as FileSystemDirectoryHandle,
          depth + 1,
          maxDepth
        );
      }

      tree.push(node);
    }
  } catch (err) {
    console.error(`无法读取目录 ${directoryHandle.name}:`, err);
  }

return tree;
}

// JavaScript 版本
asyncfunction buildFileTree(directoryHandle, depth = 0, maxDepth = 5) {
if (depth > maxDepth) return [];

const tree = [];

try {
    forawait (const [name, handle] of directoryHandle.entries()) {
      const node = {
        name,
        kind: handle.kind,
        handle
      };

      if (handle.kind === 'file') {
        const file = await handle.getFile();
        node.size = file.size;
        node.modifiedTime = file.lastModified;
      } elseif (handle.kind === 'directory') {
        node.children = await buildFileTree(handle, depth + 1, maxDepth);
      }

      tree.push(node);
    }
  } catch (err) {
    console.error(`无法读取目录: ${err.message}`);
  }

return tree;
}

// 使用示例
asyncfunction exploreDirectory() {
try {
    const dirHandle = awaitwindow.showDirectoryPicker();
    const tree = await buildFileTree(dirHandle);
    
    // 打印树形结构
    console.log('目录树:');
    console.log(JSON.stringify(tree, null, 2));
  } catch (err) {
    console.error('选择目录失败:', err);
  }
}

创建和删除文件

代码语言:javascript
复制
async function createAndWriteFile(
  directoryHandle: FileSystemDirectoryHandle,
  fileName: string,
  content: string
): Promise<boolean> {
try {
    // getFileHandle 的 create: true 选项会自动创建文件
    const fileHandle = await directoryHandle.getFileHandle(fileName, {
      create: true
    });

    const writable = await fileHandle.createWritable();
    await writable.write(content);
    await writable.close();

    console.log(`✅ 文件 ${fileName} 已创建`);
    returntrue;
  } catch (err) {
    console.error(`创建文件失败: ${err.message}`);
    returnfalse;
  }
}

// 删除文件(注意:需要权限)
asyncfunction deleteFile(
  directoryHandle: FileSystemDirectoryHandle,
  fileName: string
): Promise<boolean> {
try {
    // 删除需要明确的 remove() 调用
    await directoryHandle.removeEntry(fileName);
    console.log(`✅ 文件 ${fileName} 已删除`);
    returntrue;
  } catch (err) {
    console.error(`删除文件失败: ${err.message}`);
    returnfalse;
  }
}

// 删除目录及其内容
asyncfunction deleteDirectory(
  parentHandle: FileSystemDirectoryHandle,
  dirName: string
): Promise<boolean> {
try {
    // recursive: true 会删除目录及其所有内容
    await parentHandle.removeEntry(dirName, { recursive: true });
    console.log(`✅ 目录 ${dirName} 及其内容已删除`);
    returntrue;
  } catch (err) {
    console.error(`删除目录失败: ${err.message}`);
    returnfalse;
  }
}

权限管理:理解浏览器如何保护用户

这是 API 设计中最巧妙的部分。File System Access API 的权限模型分三个层级:

代码语言:javascript
复制
┌─────────────────────────────────────┐
│    权限模型的三个层级               │
├─────────────────────────────────────┤
│ 1️⃣  瞬时权限(Transient)          │
│     │ 只对当前选择有效              │
│     │ 用户关闭选择器即失效          │
│     └─ 最严格,但用户体验一般       │
├─────────────────────────────────────┤
│ 2️⃣  会话权限(Session)             │
│     │ 在浏览器标签页关闭前有效       │
│     │ 刷新页面后失效                │
│     └─ 平衡安全与便利              │
├─────────────────────────────────────┤
│ 3️⃣  持久权限(Persistent)          │
│     │ 保存到 IndexedDB               │
│     │ 跨会话有效                    │
│     │ 用户可在设置中撤销            │
│     └─ 最便利,但最需谨慎          │
└─────────────────────────────────────┘

在实际代码中,如何实现持久权限呢?

代码语言:javascript
复制
// 权限管理类
class FileSystemPermissionManager {
private dbName = 'FSAccessDB';
private storeName = 'fileHandles';

async saveFileHandle(key: string, handle: FileSystemFileHandle): Promise<void> {
    const db = awaitthis.openDB();
    const tx = db.transaction(this.storeName, 'readwrite');
    await tx.objectStore(this.storeName).put(handle, key);
  }

async getFileHandle(key: string): Promise<FileSystemFileHandle | null> {
    const db = awaitthis.openDB();
    const handle = await db.get(this.storeName, key);
    return handle || null;
  }

async removeFileHandle(key: string): Promise<void> {
    const db = awaitthis.openDB();
    await db.delete(this.storeName, key);
  }

private openDB(): Promise<IDBDatabase> {
    returnnewPromise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName);
        }
      };
    });
  }
}

// 使用示例
const permManager = new FileSystemPermissionManager();

asyncfunction openProjectFolder() {
try {
    const dirHandle = awaitwindow.showDirectoryPicker();
    
    // 保存权限
    await permManager.saveFileHandle('project-root', dirHandle asany);
    
    console.log('✅ 项目文件夹已保存,下次可以直接访问');
  } catch (err) {
    console.error('打开项目文件夹失败:', err);
  }
}

asyncfunction restoreProjectFolder() {
const dirHandle = await permManager.getFileHandle('project-root');

if (dirHandle) {
    console.log('✅ 已恢复项目文件夹访问权限');
    return dirHandle;
  } else {
    console.log('⚠️ 没有保存的权限,请重新选择');
    returnnull;
  }
}

关键问题:用户的权限可以被撤销吗?

是的。在浏览器设置中,用户可以随时查看哪些网站有文件系统权限,并撤销。因此你的代码应该有降级方案:

代码语言:javascript
复制
async function safeReadFile(
  fileHandle: FileSystemFileHandle
): Promise<string | null> {
try {
    // 先验证权限
    const permission = await fileHandle.queryPermission({ mode: 'read' });
    
    if (permission === 'denied') {
      // 权限被撤销,请求重新授权
      const newPermission = await fileHandle.requestPermission({ mode: 'read' });
      if (newPermission !== 'granted') {
        thrownewError('用户拒绝了权限请求');
      }
    }

    const file = await fileHandle.getFile();
    returnawait file.text();
  } catch (err) {
    console.error('读取文件失败:', err);
    returnnull;
  }
}

实战场景:构建一个简易代码编辑器

让我们整合以上知识,构建一个真实可用的代码编辑器:

代码语言:javascript
复制
class SimpleCodeEditor {
private currentFile: FileSystemFileHandle | null = null;
private isDirty = false;
private editor: HTMLTextAreaElement;
private statusDiv: HTMLDivElement;

constructor(editorSelector: string, statusSelector: string) {
    this.editor = document.querySelector(editorSelector) as HTMLTextAreaElement;
    this.statusDiv = document.querySelector(statusSelector) as HTMLDivElement;

    // 监听编辑器变化
    this.editor.addEventListener('input', () => {
      this.isDirty = true;
      this.updateStatus('有未保存的改动 💾');
    });

    // 快捷键: Ctrl+O 打开,Ctrl+S 保存
    document.addEventListener('keydown', (e) => {
      if (e.ctrlKey || e.metaKey) {
        if (e.key === 'o') {
          e.preventDefault();
          this.open();
        }
        if (e.key === 's') {
          e.preventDefault();
          this.save();
        }
      }
    });
  }

async open(): Promise<void> {
    try {
      const [fileHandle] = awaitwindow.showOpenFilePicker({
        types: [
          {
            description: 'Code Files',
            accept: {
              'text/plain': ['.js', '.ts', '.jsx', '.tsx'],
              'text/html': ['.html'],
              'application/json': ['.json']
            }
          }
        ]
      });

      const file = await fileHandle.getFile();
      const content = await file.text();

      this.editor.value = content;
      this.currentFile = fileHandle;
      this.isDirty = false;

      this.updateStatus(`📂 已打开: ${file.name}`);
    } catch (err) {
      console.error('打开文件失败:', err);
    }
  }

async save(): Promise<void> {
    if (!this.isDirty) {
      this.updateStatus('✅ 没有改动需要保存');
      return;
    }

    if (!this.currentFile) {
      // 第一次保存,需要选择位置
      awaitthis.saveAs();
      return;
    }

    try {
      const writable = awaitthis.currentFile.createWritable();
      await writable.write(this.editor.value);
      await writable.close();

      this.isDirty = false;
      this.updateStatus('✅ 已保存');
    } catch (err) {
      console.error('保存失败:', err);
      this.updateStatus('❌ 保存失败');
    }
  }

async saveAs(): Promise<void> {
    try {
      const fileHandle = awaitwindow.showSaveFilePicker({
        suggestedName: 'untitled.js',
        types: [
          {
            description: 'JavaScript',
            accept: { 'text/javascript': ['.js'] }
          },
          {
            description: 'TypeScript',
            accept: { 'text/typescript': ['.ts'] }
          }
        ]
      });

      const writable = await fileHandle.createWritable();
      await writable.write(this.editor.value);
      await writable.close();

      this.currentFile = fileHandle;
      this.isDirty = false;

      this.updateStatus(`✅ 已保存为: ${fileHandle.name}`);
    } catch (err) {
      console.error('另存为失败:', err);
    }
  }

private updateStatus(message: string): void {
    this.statusDiv.textContent = message;
  }
}

// 使用示例
const editor = new SimpleCodeEditor('textarea#code-editor', 'div#status');

对应的 HTML:

代码语言:javascript
复制
<!DOCTYPE html>
<html>
<head>
<title>File System API 代码编辑器</title>
<style>
    body {
      font-family: 'Monaco', 'Menlo', monospace;
      margin: 0;
      padding: 20px;
      background: #1e1e1e;
      color: #e0e0e0;
    }
    
    textarea {
      width: 100%;
      height: 500px;
      background: #252526;
      color: #e0e0e0;
      border: none;
      padding: 15px;
      font-family: inherit;
      font-size: 14px;
      border-radius: 4px;
    }
    
    #status {
      margin-top: 10px;
      padding: 10px;
      background: #333;
      border-radius: 4px;
      font-size: 12px;
    }
  </style>
</head>
<body>
<h1>📝 代码编辑器</h1>
<p>快捷键: Ctrl+O 打开 | Ctrl+S 保存</p>

<textarea id="code-editor" placeholder="开始编写代码..."></textarea>
<div id="status">准备就绪</div>

<script type="module" src="editor.ts"></script>
</body>
</html>

浏览器支持与降级方案

截至 2024 年,File System Access API 的支持情况如下:

浏览器

支持情况

特殊说明

Chrome

✅ 完全支持

86+ 版本

Edge

✅ 完全支持

79+ 版本

Firefox

⚠️ 试验阶段

需要标志启用

Safari

❌ 暂无

仍在考虑阶段

降级方案(支持不支持的浏览器):

代码语言:javascript
复制
async function modernFileAccess(callback: (content: string) => void) {
// 检查 API 支持
if ('showOpenFilePicker'inwindow) {
    // 使用 File System API
    try {
      const [fileHandle] = await (windowasany).showOpenFilePicker();
      const file = await fileHandle.getFile();
      const content = await file.text();
      callback(content);
    } catch (err) {
      console.error('使用现代 API 失败:', err);
    }
  } else {
    // 降级到传统 FileReader + input[type="file"]
    const input = document.createElement('input');
    input.type = 'file';
    
    input.onchange = async () => {
      const file = (input.files as FileList)[0];
      const reader = new FileReader();
      
      reader.onload = (e) => {
        callback(e.target?.result asstring);
      };
      
      reader.readAsText(file);
    };
    
    input.click();
  }
}

安全思考:API 设计如何保护用户

File System Access API 的安全模型有以下特点:

  1. 没有默认权限:必须通过 showXxxPicker() 让用户主动选择
  2. 细粒度权限:用户授予的是对特定文件/目录的权限,不是整个系统
  3. 权限检查:可通过 queryPermission() 验证权限状态
  4. 权限撤销:用户可在浏览器设置中随时撤销
  5. 沙箱隔离:不同源的网站无法访问对方获得的权限

但是,开发者需要注意的"陷阱":

错误做法:假设权限永远存在

代码语言:javascript
复制
// ❌ 这样很危险
async function dangerousRead() {
  // 用户之前授权过,这次不检查,直接读取
  const file = await this.fileHandle.getFile();
  return await file.text();
}

正确做法:每次都检查权限

代码语言:javascript
复制
// ✅ 正确的做法
asyncfunction safeRead() {
const permission = awaitthis.fileHandle.queryPermission({ mode: 'read' });

if (permission !== 'granted') {
    const newPerm = awaitthis.fileHandle.requestPermission({ mode: 'read' });
    if (newPerm !== 'granted') thrownewError('权限被拒绝');
  }

const file = awaitthis.fileHandle.getFile();
returnawait file.text();
}

总结与思考

File System Access API 是一个"突破但不失控"的完美例子。它:

  • 突破了限制:让 Web 应用获得原本不可能的能力
  • 守住了底线:通过权限模型确保用户主权
  • 设计优雅:句柄、流、异步/等待的组合非常符合现代 JavaScript 风格

对于中国的开发者,这个 API 开启了一类全新的应用可能性:

🎯 立即可用

  • 在线代码编辑器(如 Aliyun IDE)
  • 本地文件管理工具
  • 配置文件编辑器

🔮 未来前景

  • AI 与本地文件的深度集成
  • 离线优先的 Web 应用
  • 跨平台的 Electron 替代品

但记住:伟大的能力带来伟大的责任。在使用这个 API 时,始终问自己三个问题:

  1. 我真的需要本地文件访问吗? 云存储方案通常更安全
  2. 用户明白我要做什么吗? 不要偷偷获取权限
  3. 我如何优雅地处理权限被撤销? 做好降级方案

关于我

我是前端达人的内容创作者。专注于深度技术文章、源码解读和前端架构分析。

如果你对现代 JavaScript、React 19、TypeScript 或构建工具感兴趣,欢迎:

👉 关注《前端达人》公众号 - 获取最新技术深度文章

👉 在评论区讨论 - 你对 File System API 有什么实际用途吗?是否遇到过权限问题?

👉 点赞与分享 - 将这篇文章推荐给更多需要它的开发者朋友 💫

看完有收获?点个赞吧! 你的每一个点赞都是我继续深度创作的动力。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 浏览器沙箱的困局:为什么 Web 应用需要这个 API?
  • 核心概念:从文件选择器到句柄的魔法转换
  • 深入代码:打开文件的三种姿势
    • 方式 1:单文件选择(最常见)
    • 方式 2:多文件选择与文件元信息
  • 文件写入的"赋能"与"制约"
    • 核心概念:Writable Stream(可写流)
  • 目录操作:构建本地文件浏览器
    • 递归读取目录结构
    • 创建和删除文件
  • 权限管理:理解浏览器如何保护用户
  • 实战场景:构建一个简易代码编辑器
  • 浏览器支持与降级方案
  • 安全思考:API 设计如何保护用户
  • 总结与思考
  • 关于我
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档