在Vue开发中,深克隆用于复制响应式数据,确保修改副本不影响原对象,同时不丢失响应式特性。本文从浅克隆与深克隆的区别入手,介绍三种实用方案,覆盖简单数据、复杂数据类型和Vue响应式对象。
一、浅克隆与深克隆的核心区别
浅克隆只复制对象的第一层属性,嵌套对象仍共享引用。例如Object.assign()和扩展运算符(...)均为浅克隆。深克隆递归复制所有层级,副本与原对象完全独立。在Vue中,直接克隆reactive或ref对象会丢失响应式,需要特殊处理。
二、方案1:基础版深克隆(JSON序列化)
适用于不包含函数、Date、RegExp、undefined等复杂类型的普通对象和数组。利用JSON.stringify()序列化再JSON.parse()反序列化,代码极简。
- function deepCloneBasic(obj) {
- if (obj === null || typeof obj !== 'object') {
- return obj;
- }
- return JSON.parse(JSON.stringify(obj));
- }
复制代码
测试示例:- 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、Symbol等类型,会丢失或变形。
三、方案2:完善版深克隆(递归处理所有数据类型)
比基础版更通用,递归遍历对象,对Date、RegExp等特殊类型分别创建新实例,并区分数组和普通对象。
- function deepClonePerfect(obj) {
- if (obj === null || typeof obj !== 'object') {
- return obj;
- }
- if (obj instanceof Date) {
- return new Date(obj);
- }
- if (obj instanceof RegExp) {
- return new RegExp(obj.source, obj.flags);
- }
- const cloneObj = Array.isArray(obj) ? [] : {};
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- cloneObj[key] = deepClonePerfect(obj[key]);
- }
- }
- return cloneObj;
- }
复制代码
测试复杂数据:- 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
复制代码
优点:克隆彻底,所有类型无误;缺点:代码稍长,但无外部依赖。
四、方案3:Vue专属深克隆(保留响应式)
针对Vue2和Vue3的响应式数据(ref、reactive、observable),克隆后重新创建响应式对象,避免丢失响应式特性。
Vue3 专属实现:- import { ref, reactive, isRef, isReactive, toRaw } from 'vue'
- function deepCloneVue3(obj) {
- if (isRef(obj)) {
- const rawValue = toRaw(obj.value);
- const cloneValue = deepClonePerfect(rawValue);
- return ref(cloneValue);
- }
- if (isReactive(obj)) {
- const rawObj = toRaw(obj);
- const cloneObj = deepClonePerfect(rawObj);
- return reactive(cloneObj);
- }
- 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]
复制代码
Vue2 专属实现:- import Vue from 'vue'
- function deepCloneVue2(obj) {
- if (Vue.isReactive(obj) || Vue.isVue(obj)) {
- const TempComponent = Vue.extend({
- props: Object.keys(obj),
- render: function() {}
- });
- const instance = new TempComponent({ propsData: obj });
- return instance._props;
- }
- if (obj && obj._isRef) {
- const cloneValue = deepClonePerfect(obj.value);
- return Vue.ref(cloneValue);
- }
- return deepClonePerfect(obj);
- }
复制代码
测试:- 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版本,需区分Vue2和Vue3写法。
五、注意事项
- 不要用JSON方案克隆响应式数据,会丢失响应式。
- Vue3中克隆reactive对象前需用toRaw()转为原始对象,避免代理干扰。
- 避免克隆Vue组件实例、Vuex状态等特殊对象,可能引发异常。
- 频繁使用时建议封装为全局工具函数(如utils/clone.js)。
- 简单场景用方案1,复杂非响应式数据用方案2,响应式数据用方案3。
六、总结与选择建议
- 普通对象/数组无复杂类型:方案1(JSON序列化),简洁高效。
- 所有数据类型(非响应式):方案2(递归完善版),克隆彻底。
- Vue响应式数据(ref/reactive/observable):方案3(Vue专属版),保留响应式。
实际开发中可组合方案2和方案3,封装后按需调用,提升代码复用性。 |