深克隆与浅克隆的核心概念
- 基础前提:基本类型 vs 引用类型
类型 存储位置 复制行为 示例 基本类型 栈内存 复制值 (独立副本) Number、String、Boolean、Null、Undefined、Symbol、BigInt 引用类型 堆内存 复制引用 (指向同一堆内存) Object、Array、Function、RegExp、Date 等 - 浅克隆 (Shallow Clone)
- 定义:仅复制对象的
「表层属性」—— 基本类型属性复制值,引用类型属性复制引用 (新旧对象共享同一引用类型数据); - 特点:修改新对象的引用类型属性,会同步影响原对象;
- 定义:仅复制对象的
- 深克隆 (Deep Clone)
- 定义:递归复制对象的
所有层级属性—— 无论是基本类型还是引用类型,都创建独立副本,新旧对象完全隔离; - 特点:修改新对象的任意属性,都不会影响原对象;
- 定义:递归复制对象的
浅克隆的多种实现方式
手动遍历赋值(通用版)
-
手动遍历对象 / 数组的表层属性,逐个复制值,适用于所有引用类型,可控性最强;
-
实现代码:
function shallowClone(target) { // 排除非引用类型(基本类型直接返回自身) if (typeof target !== 'object' || target === null) return target; // 根据类型初始化克隆结果 const cloneTarget = Array.isArray(target) ? [] : {}; // 遍历表层属性(仅自身可枚举属性,不包含原型链) for (let key in target) { if (target.hasOwnProperty(key)) { cloneTarget[key] = target[key]; } } return cloneTarget; } // 测试 const obj = { a: 1, b: [1, 2], c: { d: 3 } }; const cloneObj = shallowClone(obj); cloneObj.a = 2; cloneObj.b.push(3); // 原对象 obj.b 也会变成 [1,2,3] cloneObj.c.d = 4; // 原对象 obj.c.d 也会变成 4 console.log(obj); // { a: 1, b: [ 1, 2, 3 ], c: { d: 4 } } console.log(cloneObj); // { a: 2, b: [ 1, 2, 3 ], c: { d: 4 } }
Object.assign ()(对象专用)
-
ES6 新增方法,将一个或多个源对象的自身可枚举属性复制到目标对象,返回目标对象;
-
注意:仅克隆表层,且无法克隆 Symbol 类型属性 (需手动处理);
-
实现代码:
const obj = { a: 1, b: [1, 2], c: { d: 3 } }; const cloneObj = Object.assign({}, obj); cloneObj.a = 2; cloneObj.b.push(3); // 原对象 obj.b 同步变化 cloneObj.c.d = 4; // 原对象 obj.c.d 同步变化 console.log(obj); // { a: 1, b: [ 1, 2, 3 ], c: { d: 4 } } console.log(cloneObj); // { a: 2, b: [ 1, 2, 3 ], c: { d: 4 } }
数组专用浅克隆方法
-
数组的内置方法仅复制表层元素,属于浅克隆;
-
实现代码:
const arr = [1, [2, 3], { a: 4 }]; // 方式1:扩展运算符 const cloneArr1 = [...arr]; cloneArr1.push({ b: 5 }); cloneArr1[1].push(4); cloneArr1[2].a = 5; console.log(arr); // [ 1, [ 2, 3, 4 ], { a: 5 } ] console.log(cloneArr1); // [ 1, [ 2, 3, 4 ], { a: 5 }, { b: 5 } ]const arr = [1, [2, 3], { a: 4 }]; // 方式2:Array.prototype.slice() const cloneArr2 = arr.slice(); cloneArr2.push({ b: 5 }); cloneArr2[1].push(4); cloneArr2[2].a = 5; console.log(arr); // [ 1, [ 2, 3, 4 ], { a: 5 } ] console.log(cloneArr2); // [ 1, [ 2, 3, 4 ], { a: 5 }, { b: 5 } ]const arr = [1, [2, 3], { a: 4 }]; // 方式3:Array.prototype.concat() const cloneArr3 = arr.concat(); cloneArr3.push({ b: 5 }); cloneArr3[1].push(4); cloneArr3[2].a = 5; console.log(arr); // [ 1, [ 2, 3, 4 ], { a: 5 } ] console.log(cloneArr3); // [ 1, [ 2, 3, 4 ], { a: 5 }, { b: 5 } ]const arr = [1, [2, 3], { a: 4 }]; // 方式4:Array.from() const cloneArr4 = Array.from(arr); cloneArr4.push({ b: 5 }); cloneArr4[1].push(4); cloneArr4[2].a = 5; console.log(arr); // [ 1, [ 2, 3, 4 ], { a: 5 } ] console.log(cloneArr4); // [ 1, [ 2, 3, 4 ], { a: 5 }, { b: 5 } ]
扩展运算符(对象 / 数组通用)
-
ES6 扩展运算符
...可快速浅克隆对象 / 数组; -
实现代码:
const obj = { a: 1, b: [2, 3] }; const cloneObj = { ...obj }; cloneObj.a = 2; cloneObj.b.push(4); console.log(obj); // { a: 1, b: [ 2, 3, 4 ] } console.log(cloneObj); // { a: 2, b: [ 2, 3, 4 ] }const arr = [1, { x: 2 }]; const cloneArr = [...arr]; cloneArr[0] = 2; cloneArr[1].x = 3; console.log(arr); // [ 1, { x: 3 } ] console.log(cloneArr); // [ 2, { x: 3 } ]
Object.create ()(原型继承式克隆)
-
通过继承原对象的原型,克隆表层属性 (需手动复制属性);
-
实现代码:
const obj = { a: 1, b: [2, 3] }; const cloneObj = Object.create(Object.getPrototypeOf(obj)); // 继承原型 Object.assign(cloneObj, obj); // 复制表层属性 cloneObj.a = 2; cloneObj.b.push(4); console.log(obj); // { a: 1, b: [ 2, 3, 4 ] } console.log(cloneObj); // { a: 2, b: [ 2, 3, 4 ] }
深克隆的多种实现方式
JSON 序列化 / 反序列化
-
原理:将对象转为 JSON 字符串,再解析为新对象,自动递归复制;
-
局限性:
- 无法克隆 Function、Symbol、undefined 属性 (会丢失);
- 无法克隆循环引用的对象 (报错);
- 无法克隆 RegExp、Date (转为字符串 / 数字);
- 无法克隆原型链上的属性;
- 数值精度可能丢失 (如 BigInt 转为数字);
-
实现代码:
function deepCloneJSON(target) { return JSON.parse(JSON.stringify(target)); } // 测试 const obj = { a: 1, b: [2, 3], c: { d: 4 }, e: () => { }, // 丢失 f: Symbol('foo'), // 丢失 g: undefined, // 丢失 h: new Date(), // 转为字符串 i: /abc/, // 转为空对象 {} }; console.log(deepCloneJSON(obj)); /** { a: 1, b: [ 2, 3 ], c: { d: 4 }, h: '2025-12-18T05:38:47.944Z', i: {} } */
递归手动克隆(基础版,处理核心类型)
-
手动递归遍历对象,区分类型复制,解决 JSON 方法的部分局限性;
-
实现代码:
function deepCloneBasic(target) { // 基本类型直接返回 if (typeof target !== 'object' || target === null) return target; // 处理 Date if (target instanceof Date) return new Date(target); // 处理 RegExp if (target instanceof RegExp) return new RegExp(target.source, target.flags); // 初始化克隆结果(保留数组/对象类型) const cloneTarget = Array.isArray(target) ? [] : {}; // 遍历所有自身可枚举属性(包含 Symbol 类型) Reflect.ownKeys(target).forEach(key => { // 递归克隆属性值 cloneTarget[key] = deepCloneBasic(target[key]); }); return cloneTarget; } // 测试 const obj = { a: 1, b: [2, { x: 3 }], c: new Date(), d: /abc/g, e: Symbol('foo'), }; const cloneObj = deepCloneBasic(obj); cloneObj.b[1].x = 4; console.log(cloneObj); /** { a: 1, b: [ 2, { x: 4 } ], c: 2025-12-18T05:45:17.433Z, d: /abc/g, e: Symbol(foo) } */
递归克隆 + 循环引用处理(完整版)
-
解决 「循环引用」 问题 (如 obj.self = obj),通过 WeakMap 缓存已克隆的对象;
-
实现代码:
function deepCloneComplete(target, cache = new WeakMap()) { // 1. 基本类型直接返回 if (typeof target !== 'object' || target === null) return target; // 2. 处理循环引用:缓存已克隆的对象 if (cache.has(target)) return cache.get(target); // 3. 处理特殊内置类型 // 3.1 Date if (target instanceof Date) { const clone = new Date(target); cache.set(target, clone); return clone; } // 3.2 RegExp if (target instanceof RegExp) { const clone = new RegExp(target.source, target.flags); cache.set(target, clone); return clone; } // 3.3 ArrayBuffer if (target instanceof ArrayBuffer) { const clone = target.slice(0); cache.set(target, clone); return clone; } // 3.4 类型化数组(如 Uint8Array、Float64Array) if (ArrayBuffer.isView(target)) { const clone = new target.constructor(target.buffer.slice(0)); cache.set(target, clone); return clone; } // 3.5 Set if (target instanceof Set) { const clone = new Set(); cache.set(target, clone); target.forEach(item => clone.add(deepCloneComplete(item, cache))); return clone; } // 3.6 Map if (target instanceof Map) { const clone = new Map(); cache.set(target, clone); target.forEach((value, key) => clone.set(key, deepCloneComplete(value, cache))); return clone; } // 4. 处理普通对象(保留原型链) const cloneTarget = Object.create(Object.getPrototypeOf(target)); cache.set(target, cloneTarget); // 先缓存,避免递归循环 // 5. 遍历所有自身属性(包含不可枚举、Symbol 类型) // 核心修正:Object.getOwnPropertyDescriptors 返回对象,需用 Object.entries 遍历 const descriptors = Object.getOwnPropertyDescriptors(target); Object.entries(descriptors).forEach(([key, desc]) => { // 递归克隆属性值,并重写描述符(保留 writable/enumerable/configurable 等特性) desc.value = deepCloneComplete(target[key], cache); Object.defineProperty(cloneTarget, key, desc); }); return cloneTarget; } // 测试用例 // 1. 基础类型 + 引用类型 const obj1 = { a: 1, b: [2, 3, { c: 4 }], d: new Date(), e: /abc/gi, f: Symbol('test'), g: new Set([1, 2]), h: new Map([['key', 'value']]) }; const clone1 = deepCloneComplete(obj1); console.log(clone1.b[2].c = 5); // 修改克隆对象,原对象不受影响 console.log(obj1.b[2].c); // 4(原对象未变) // 2. 循环引用 const obj2 = { x: 10 }; obj2.self = obj2; // 循环引用 const clone2 = deepCloneComplete(obj2); console.log(clone2.self === clone2); // true(克隆后仍保持循环引用) console.log(clone2.x); // 10 // 3. 不可枚举属性 const obj3 = {}; Object.defineProperty(obj3, 'hidden', { value: '不可枚举', enumerable: false }); const clone3 = deepCloneComplete(obj3); console.log(Object.getOwnPropertyDescriptor(clone3, 'hidden').value); // 不可枚举 console.log(Object.keys(clone3).includes('hidden')); // false(保留不可枚举特性)
第 2️⃣ 座大山:this 的核心特性
上一篇