structuredclone()可克隆file但不能克隆filesystemhandle,因file是可序列化数据载体,而filesystemhandle是绑定origin和授权的活权限代理;混合对象需拆解处理:单独克隆file部分,通过postmessage传递或用id重获handle。

structuredClone() 能优雅克隆含 File 的对象,但不能克隆 FileSystemHandle——这是关键分界线。它不是能力不足,而是设计使然:File 是可序列化的数据载体,FileSystemHandle 是活的权限代理。混用二者时,必须拆解处理,而非强行统一克隆。
含 File 的部分可直接克隆,且效果可靠
File(以及 Blob、ArrayBuffer 等)是 structuredClone 原生支持的类型,克隆后:
- 新
File实例保持.name、.type、.lastModified和二进制内容完整 - 可直接用于
FormData.append()、fetch()或URL.createObjectURL()(注意:object URL 需重新生成) - 元数据应与
File分离存储,避免挂载在实例上(克隆会丢弃自定义属性)
示例安全结构:
const formData = {
file: input.files[0], // 原生 File 实例 ✅ 可克隆
metadata: { id: 'upload-123', purpose: 'avatar' } // 独立字段 ✅ 保留完好
};
const cloned = structuredClone(formData); // 完全可用
FileSystemHandle 必须绕过克隆,改用传递或重获
FileSystemHandle(包括 FileSystemFileHandle 和 FileSystemDirectoryHandle)调用 structuredClone(handle) 会立即抛出 DataCloneError。原因在于:
- 它绑定具体页面 origin、用户显式授权上下文和底层操作系统令牌
- 权限状态不可序列化,不是“数据”,而是“能力凭证”
正确做法只有两种:
-
跨上下文传递引用:通过
postMessage(handle, [handle])将其作为 transferable 发送给 Worker 或 iframe(现代浏览器支持) -
在目标环境重新获取:利用持久化 ID 静默重建,例如:
// 首次获取并记住位置 const handle = await self.showDirectoryPicker({ id: 'user-docs' }); // 后续直接复用(无需用户再次点击) const sameHandle = await self.showDirectoryPicker({ id: 'user-docs' });
混合对象需分层处理,不能一锅炖
当一个对象同时含 File 和 FileSystemHandle(如 { configFile, rootDirHandle }),不能整体 structuredClone()。应拆开操作:
- 单独克隆含
File的子树 - 单独传递或重获
FileSystemHandle字段 - 最终在目标环境组装
// 原始对象(混合)
const appState = {
tempUpload: input.files[0], // ✅ 可克隆
workspace: directoryHandle // ❌ 不可克隆
};
// 正确处理方式
const clonedData = structuredClone({
tempUpload: appState.tempUpload
});
// handle 单独 postMessage 或存 ID 后重取
worker.postMessage({ data: clonedData, handleId: 'workspace-root' });
注意权限生命周期,避免“句柄失效”陷阱
FileSystemHandle 的有效性高度依赖运行环境:
- 刷新页面或关闭标签页后,未持久化的 handle 自动失效
- Worker 中无法调用
showOpenFilePicker(),必须由主线程获取后传入 -
requestPermission()在部分场景下已弃用,优先用show*Picker()配合id参数实现静默恢复
不复杂但容易忽略:handle 是活的权限代理,不是静态数据——设计时应围绕“传递”和“重获”构建流程,而非尝试复制它。











