vue3中DataCloneError: Failed to execute ‘structuredClone‘ on ‘Window‘: #<Object> could not be cloned
方案优点缺点推荐度简单、无需依赖、处理大部分基本类型丢失函数、Date、undefined等⭐⭐⭐⭐功能全面、可靠、保留更多类型需要引入第三方库⭐⭐⭐⭐⭐浏览器原生、性能好需要处理Vue代理、可能仍有兼容问题⭐⭐自定义深拷贝函数完全可控、可定制实现复杂、容易出错⭐⭐根据你的错误信息和数据结构,推荐用或 JSON.parse(JSON.stringify())。
·
// 监视信息对话框状态,同步数据
watch(infoDialogVisible, (newValue) => {
if (newValue) {
// 对话框打开时,从store复制数据到本地副本
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// Object.assign(localCapitalInfo, sourceData); // ❌浅拷贝:直接引用源对象,修改源数据可能影响目标,修改localCapitalInfo会影响sourceData,从而影响emptyCapitalInfo
// Object.assign(localCapitalInfo, { ...sourceData }); // ✅浅拷贝:创建新对象,与源对象解耦,与源数据隔离,更安全,修改localCapitalInfo不会影响sourceData和emptyCapitalInfo
// Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData))); // ✅深拷贝:因为localCapitalInfo为reactive对象,不能直接赋值,如果是ref对象,可以 localCapitalInfo.value = JSON.parse(JSON.stringify(sourceData));
Object.assign(localCapitalInfo, structuredClone(sourceData)); // ✅深拷贝:现代浏览器原生方法
// 文件选择器实例清空资源,以便可以继续选择文件,不然会提示文件限制
baseFileSelectorRef.value?.cleanupResources();
}
});
报错:CapitalInfo.vue:415 Uncaught (in promise) DataCloneError: Failed to execute 'structuredClone' on 'Window': #<Object> could not be cloned.
at CapitalInfo.vue:415:1
这个错误是因为 structuredClone 无法克隆某些特殊类型的对象。在你的场景中,sourceData 很可能包含:
-
Vue3 的响应式代理对象(reactive/proxy)
-
函数或方法
-
Symbol 类型的属性
-
循环引用
-
特殊的 DOM 对象
解决方案
方案1:使用 JSON.parse(JSON.stringify())(推荐)
typescript
watch(infoDialogVisible, (newValue) => {
if (newValue) {
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 使用 JSON 方法进行深拷贝
Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData)));
baseFileSelectorRef.value?.cleanupResources();
}
});
方案2:使用 toRaw() 剥离响应式代理
typescript
import { toRaw } from 'vue';
watch(infoDialogVisible, (newValue) => {
if (newValue) {
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 先剥离响应式代理,再使用 structuredClone
const rawData = sourceData === emptyCapitalInfo
? sourceData
: toRaw(sourceData);
Object.assign(localCapitalInfo, structuredClone(rawData));
baseFileSelectorRef.value?.cleanupResources();
}
});
方案3:使用 lodash.cloneDeep
typescript
import { cloneDeep } from 'lodash-es';
watch(infoDialogVisible, (newValue) => {
if (newValue) {
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 使用 lodash 的深拷贝
Object.assign(localCapitalInfo, cloneDeep(sourceData));
baseFileSelectorRef.value?.cleanupResources();
}
});
方案4:安全的 structuredClone 封装
typescript
function safeStructuredClone(obj: any): any {
try {
// 尝试使用 structuredClone
return structuredClone(obj);
} catch (error) {
console.warn('structuredClone failed, falling back to JSON:', error);
// 降级到 JSON 方法
return JSON.parse(JSON.stringify(obj));
}
}
watch(infoDialogVisible, (newValue) => {
if (newValue) {
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 使用安全的深拷贝函数
Object.assign(localCapitalInfo, safeStructuredClone(sourceData));
baseFileSelectorRef.value?.cleanupResources();
}
});
方案5:处理函数和特殊类型的深拷贝
typescript
function deepCloneForVue(obj: any): any {
// 处理基本类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理数组
if (Array.isArray(obj)) {
return obj.map(item => deepCloneForVue(item));
}
// 处理普通对象(剥离 Vue 的响应式代理)
const rawObj = toRaw(obj);
const cloned = {};
for (const key in rawObj) {
// 跳过 Vue 的内部属性
if (key.startsWith('__v_')) continue;
const value = rawObj[key];
// 处理函数
if (typeof value === 'function') {
// 可以保留函数引用,或者创建一个新的绑定
cloned[key] = value.bind(cloned);
}
// 处理 Date
else if (value instanceof Date) {
cloned[key] = new Date(value.getTime());
}
// 递归处理对象
else if (value && typeof value === 'object') {
cloned[key] = deepCloneForVue(value);
}
// 基本类型
else {
cloned[key] = value;
}
}
return cloned;
}
watch(infoDialogVisible, (newValue) => {
if (newValue) {
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 使用自定义的 Vue 友好深拷贝
Object.assign(localCapitalInfo, deepCloneForVue(sourceData));
baseFileSelectorRef.value?.cleanupResources();
}
});
针对你代码的最佳实践
考虑到你的数据类型 CapitalInfo 主要是基本类型和数组,推荐使用 方案1 或 方案3:
推荐方案1:JSON 方法(最简单)
typescript
watch(infoDialogVisible, (newValue) => {
if (newValue) {
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 简单可靠,适用于你的数据结构
Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData)));
baseFileSelectorRef.value?.cleanupResources();
}
});
推荐方案3:使用 lodash(最专业)
typescript
import { cloneDeep } from 'lodash-es';
watch(infoDialogVisible, (newValue) => {
if (newValue) {
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 专业可靠的深拷贝
Object.assign(localCapitalInfo, cloneDeep(sourceData));
baseFileSelectorRef.value?.cleanupResources();
}
});
为什么 structuredClone 会失败?
在你的情况下,最可能的原因是:
-
sourceData 是 Vue 的响应式代理对象
-
structuredClone无法克隆 Proxy 对象 -
需要使用
toRaw()先获取原始对象
-
-
对象中包含 Vue 的内部属性
-
如
__v_skip、__v_isRef等 -
这些是 Vue 内部使用的,不可克隆
-
-
可能有循环引用
-
虽然你的数据结构看起来没有,但 Vue 的响应式系统可能会创建
-
完整修复示例
typescript
import { watch } from 'vue';
import { cloneDeep } from 'lodash-es';
// 监视信息对话框状态,同步数据
watch(infoDialogVisible, (newValue) => {
if (newValue) {
// 对话框打开时,从store复制数据到本地副本
const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo;
// ✅ 最佳方案:使用 lodash 的 cloneDeep
Object.assign(localCapitalInfo, cloneDeep(sourceData));
// 或者使用 JSON 方法(如果确定没有函数、Date等特殊类型)
// Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData)));
// 文件选择器实例清空资源,以便可以继续选择文件,不然会提示文件限制
baseFileSelectorRef.value?.cleanupResources();
}
});
总结
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| JSON.parse(JSON.stringify()) | 简单、无需依赖、处理大部分基本类型 | 丢失函数、Date、undefined等 | ⭐⭐⭐⭐ |
| lodash.cloneDeep | 功能全面、可靠、保留更多类型 | 需要引入第三方库 | ⭐⭐⭐⭐⭐ |
| toRaw + structuredClone | 浏览器原生、性能好 | 需要处理Vue代理、可能仍有兼容问题 | ⭐⭐ |
| 自定义深拷贝函数 | 完全可控、可定制 | 实现复杂、容易出错 | ⭐⭐ |
根据你的错误信息和数据结构,推荐用 lodash.cloneDeep 或 JSON.parse(JSON.stringify())。
更多推荐



所有评论(0)