深克隆(深拷贝)的核心是复制一个对象/数组,且拷贝后的对象与原对象完全独立,修改拷贝对象不会影响原对象(包括嵌套层级的属性/元素)。在Vue开发中,深克隆常用于处理响应式数据(如ref、reactive创建的数据),避免因浅拷贝导致的响应式异常,以下是3种实用实现方案,从简单到完善,覆盖不同场景。
一、核心前提:浅克隆 vs 深克隆(必懂区别)
先明确浅克隆与深克隆的差异,避免混淆:
- 浅克隆:仅复制对象的第一层属性,嵌套的对象/数组仍指向原引用(修改嵌套内容会影响原对象),如Object.assign()、扩展运算符(...)。
- 深克隆:复制对象的所有层级(包括嵌套的对象、数组、函数等),拷贝后与原对象完全脱离引用,互不影响(这是Vue开发中常用的方式)。
Vue中注意点:对于reactive创建的响应式对象,直接克隆会丢失响应式特性,需特殊处理;ref对象需先获取.value再克隆。
二、方案1:基础版深克隆(适配普通对象/数组,无复杂类型)
适用于:普通对象、数组,无函数、Date、RegExp等复杂类型,代码简洁,上手快。
核心逻辑:利用JSON.stringify()将对象转为字符串,再用JSON.parse()转回对象,实现深度拷贝(本质是序列化+反序列化)。
// 基础版深克隆(Vue中可直接使用,适配简单数据)
function deepCloneBasic(obj) {
// 处理null和非对象/数组类型(直接返回,无需克隆)
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 利用JSON序列化+反序列化实现深克隆
return JSON.parse(JSON.stringify(obj));
}
// Vue中测试示例(普通数据)
const obj = { name: 'Vue', info: { version: '3.4.21' }, list: [1, 2, 3] };
const cloneObj = deepCloneBasic(obj);
// 修改拷贝对象,原对象不受影响
cloneObj.info.version = '3.5.0';
cloneObj.list.push(4);
console.log(obj.info.version); // 3.4.21(原对象不变)
console.log(obj.list); // [1,2,3](原对象不变)
优点:代码简洁,无需复杂逻辑,适配大部分简单场景;
缺点:无法克隆函数、Date、RegExp、undefined、Symbol等复杂类型(会丢失或变形),不适合包含这些类型的数据。
三、方案2:完善版深克隆(适配所有类型,通用推荐)
适用于:所有数据类型(对象、数组、函数、Date、RegExp等),解决基础版的缺陷,是Vue开发中最常用的通用方案。
核心逻辑:递归遍历对象/数组,对不同类型的数据分别处理,确保所有层级都被深度拷贝,不丢失类型和属性。
// 完善版深克隆(适配所有类型,Vue通用)
function deepClonePerfect(obj) {
// 1. 处理null和非对象类型(直接返回)
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 2. 处理Date类型(避免Date被转为普通对象)
if (obj instanceof Date) {
return new Date(obj);
}
// 3. 处理RegExp类型(保留正则的修饰符)
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 4. 处理数组和对象(区分数组和普通对象)
// 创建一个与原对象类型一致的空容器(数组→数组,对象→对象)
const cloneObj = Array.isArray(obj) ? [] : {};
// 5. 递归遍历所有属性,深度拷贝
for (let key in obj) {
// 只拷贝对象自身的属性(排除原型链上的属性)
if (obj.hasOwnProperty(key)) {
// 递归克隆子属性(嵌套对象/数组)
cloneObj[key] = deepClonePerfect(obj[key]);
}
}
return cloneObj;
}
// Vue中测试示例(复杂数据)
const complexObj = {
name: '深克隆',
date: new Date(),
reg: /vue/g,
func: () => console.log('vue'),
info: { a: 1, b: { c: 2 } },
list: [1, [2, 3]]
};
const cloneComplex = deepClonePerfect(complexObj);
cloneComplex.info.b.c = 100;
cloneComplex.list[1].push(4);
cloneComplex.date.setFullYear(2025);
console.log(complexObj.info.b.c); // 2(原对象不变)
console.log(complexObj.list[1]); // [2,3](原对象不变)
console.log(complexObj.date.getFullYear()); // 2024(原对象不变)
console.log(cloneComplex.func()); // vue(函数正常克隆)
优点:适配所有数据类型,克隆彻底,无数据丢失,满足Vue中复杂数据的克隆需求;
缺点:代码稍复杂,无需额外依赖,可直接封装到Vue项目中复用。
四、方案3:Vue专属深克隆(适配响应式数据,推荐)
适用于:Vue2/Vue3的响应式数据(ref、reactive创建的数据),解决“克隆后丢失响应式”的问题,结合Vue内置API,更贴合Vue开发场景。
核心逻辑:利用Vue内置的reactive、ref API,克隆后重新创建响应式对象,确保克隆后的数据仍保持响应式特性。
1. Vue3 专属实现(适配ref、reactive)
<script setup>
import { ref, reactive, isRef, isReactive, toRaw } from 'vue'
// Vue3专属深克隆(保留响应式)
function deepCloneVue3(obj) {
// 处理ref对象:先获取原始值,克隆后重新创建ref
if (isRef(obj)) {
const rawValue = toRaw(obj.value); // 转为原始值(避免响应式干扰)
const cloneValue = deepClonePerfect(rawValue); // 用完善版克隆原始值
return ref(cloneValue); // 重新创建ref,保留响应式
}
// 处理reactive对象:先获取原始值,克隆后重新创建reactive
if (isReactive(obj)) {
const rawObj = toRaw(obj); // 转为原始对象
const cloneObj = deepClonePerfect(rawObj); // 深度克隆
return reactive(cloneObj); // 重新创建reactive,保留响应式
}
// 非响应式数据,直接用完善版克隆
return deepClonePerfect(obj);
}
// 测试示例
const reactiveObj = reactive({ name: 'Vue3', info: { age: 5 } });
const refObj = ref({ a: 1, b: [2, 3] });
// 克隆响应式数据
const cloneReactive = deepCloneVue3(reactiveObj);
const cloneRef = deepCloneVue3(refObj);
// 修改克隆对象,原对象不受影响,且克隆对象仍有响应式
cloneReactive.info.age = 10;
cloneRef.value.b.push(4);
console.log(reactiveObj.info.age); // 5(原对象不变)
console.log(refObj.value.b); // [2,3](原对象不变)
console.log(cloneReactive.info.age); // 10(克隆对象响应式正常)
</script>
2. Vue2 专属实现(适配data、ref、reactive)
// Vue2专属深克隆(保留响应式,需引入Vue)
import Vue from 'vue'
function deepCloneVue2(obj) {
// 处理Vue2的响应式对象(data中定义的对象、Vue.observable创建的对象)
if (Vue.isReactive(obj) || Vue.isVue(obj)) {
// 用Vue.extend创建临时组件,通过props传递数据,实现响应式克隆
const TempComponent = Vue.extend({
props: Object.keys(obj),
render: function() {}
});
const instance = new TempComponent({ propsData: obj });
return instance._props;
}
// 处理ref对象(Vue2的ref需通过.value访问)
if (obj && obj._isRef) {
const cloneValue = deepClonePerfect(obj.value);
return Vue.ref(cloneValue);
}
// 非响应式数据,用完善版克隆
return deepClonePerfect(obj);
}
// 测试示例(Vue2)
const vue2Reactive = Vue.observable({ name: 'Vue2', list: [1, 2] });
const cloneVue2 = deepCloneVue2(vue2Reactive);
cloneVue2.list.push(3);
console.log(vue2Reactive.list); // [1,2](原对象不变)
console.log(cloneVue2.list); // [1,2,3](克隆对象响应式正常)
优点:专门适配Vue响应式数据,克隆后不丢失响应式特性,贴合Vue开发场景;
缺点:依赖Vue内置API,需区分Vue2和Vue3版本。
五、Vue中深克隆的注意事项
- 克隆响应式数据时,不可直接用基础版(JSON方式),会丢失响应式特性,需用Vue专属方案;
- Vue3中,reactive对象克隆前需用toRaw()转为原始对象,否则会因响应式代理导致克隆异常;
- 避免克隆Vue组件实例、Vuex状态等特殊对象,这类对象克隆后可能无法正常使用;
- 若项目中频繁使用深克隆,可将完善版/Vue专属版封装为全局工具函数(如utils/clone.js),方便复用;
- 简单场景(无复杂类型、非响应式数据)可直接用基础版,复杂场景优先用完善版,响应式数据用Vue专属版。
六、总结(方案选择建议)
- 简单场景(普通对象/数组,无复杂类型):方案1(基础版),简洁高效;
- 通用场景(所有数据类型,非响应式):方案2(完善版),克隆彻底,无数据丢失;
- Vue开发场景(响应式数据):方案3(Vue专属版),保留响应式,贴合Vue特性。
实际开发中,可根据数据类型和是否为响应式,选择对应的方案,建议将方案2和方案3封装为全局工具,提升开发效率。