浅拷贝常见方法
- 特点:只复制对象
第一层属性,浅套对象仍保留共享引用
-
Object.assign()
- 将原对象的可枚举属性复制到目标对象
const obj = {a: 1, next: { b: 2 }} const copy = Object.assgin({}, obj) -
展开运算符
- 通过 ... 展开对象或数组
const obj = { a: 1, nested: { b: 2 } }; const copy = { ...obj }; const arr = [1, [2, 3]]; const arrCopy = [...arr]; -
数组的slice / concat / Array.from()
- 适用于数组的浅拷贝
const arr = [1, [2, 3]]; const copy = arr.slice(); const copy2 = [].concat(arr); const copy3 = Array.from(arr)
深拷贝
- 特点:复制所有层级的属性,新旧对象
完全独立,不再有引用共享
- JSON.parse(JSON.stringify())
- 通过JSON序列化再反序列化实现
- 缺点
- 会跳过
undefined / function / Symbol类型的值 - 无法处理循环引用
日期对象转为字符串,正则表达式丢失
- 会跳过
const sampleObject = { string: 'string', number: 123, boolean: false, null: null, undefined: undefined, // 会被忽略 跳过 fn: function() {}, // 会被忽略 跳过 sy: Symbol(), // 会被忽略 跳过 date: new Date('1999-12-31T23:59:59'), // Date 将被转成字符串 notANumber: NaN, // NaN 转成null infinity: Infinity, // Infinity 转成null regExp: /.*/, // RegExp 转成空对象 } const faultyClone = JSON.parse(JSON.stringify(sampleObject)) - structuredClone() (相对简单场景,
优先使用)- 浏览器原生支持,支持更多类型(如Date,Set,Map等)
- 缺点
- 不支持 函数 / Symbol / DOM 节点
- 需要较新的浏览器环境(或Node.js 17+)
var sampleObject = { string: 'string', number: 123, boolean: false, null: null, undefined: undefined, date: new Date('1999-12-31T23:59:59'), notANumber: NaN, infinity: Infinity, regExp: /.*/, // fn: function() {}, // 报错 // sy: Symbol(1), // 报错 } var faultyClone = structuredClone(sampleObject) const obj2 = {c: function() {}}; const copy2 = structuredClone(obj); // 报错 Uncaught DataCloneError: Failed to execute 'structuredClone' on 'Window': function() {} could not be cloned. const obj3 = {sy: Symbol()}; // 报错 Uncaught DataCloneError: Failed to execute 'structuredClone' on 'Window': Symbol(1) could not be cloned. - 第三方库(如Lodash的._cloneDeep)(复杂场景,函数,特殊类型)
- 使用成熟库处理复杂场景(如循环引用/特殊对象)
const _ = require('lodash'); const copy = _.cloneDeep(obj); - 手动递归
- 简单版:处理对象和数组
const deepCopy = (obj) => { // 如果不是对象或者为 null,直接返回 if (typeof obj !== "object" || obj === null) { return obj; } // 创建一个新对象或数组 const newObj = Array.isArray(obj) ? [] : {}; Object.keys(obj).forEach((key) => { newObj[key] = deepCopy(obj[key]); }); return newObj; }; // 测试对象和数组的深拷贝: const nestedArray = [[1], [2], [3]]; const nestedCopyWithStructuredClone = deepCopy(nestedArray); // 深拷贝 console.log(nestedArray === nestedCopyWithStructuredClone); // false nestedArray[0][0] = 4; console.log(nestedArray); // [[4], [2], [3]] 修改了原对象 console.log(nestedCopyWithStructuredClone); // [[1], [2], [3]] 复制对象不受影响-
进阶版:处理循环引用
- 什么事循环引用?
const nestedObject = { name: 'Lily' } nestedObject.nestedObject = nestedObject; // 自己引用自己, 无限循环下去- 解决循环引用导致的栈内存溢出,需要用到WeakMap 记录已经复制过的对象
const deepCopy = (obj, cache = new WeakMap()) => { // 如果不是对象或者为 null,直接返回 if (typeof obj !== "object" || obj === null) { return obj; } // 如果已经复制过该对象,则直接返回 if (cache.has(obj)) { return cache.get(obj); } // 创建一个新对象或数组 const newObj = Array.isArray(obj) ? [] : {}; // 将新对象放入 cache 中 cache.set(obj, newObj); // 处理循环引用的情况 Object.keys(obj).forEach((key) => { newObj[key] = deepCopy(obj[key], cache); }); return newObj; }; // 测试有循环引用的深拷贝: const nestedObject = { name: 'Lily' } nestedObject.nestedObject = nestedObject; const nestedCopyWithStructuredClone = deepCopy(nestedObject); // 深拷贝 console.log(nestedObject === nestedCopyWithStructuredClone); // false -
最终版:处理特殊对象:Date 和 RegExp
const deepCopy = (obj, cache = new WeakMap()) => { // 如果不是对象或者为 null,直接返回 if (typeof obj !== "object" || obj === null) { return obj; } // 如果已经复制过该对象,则直接返回 if (cache.has(obj)) { return cache.get(obj); } // 创建一个新对象或数组 const newObj = Array.isArray(obj) ? [] : {}; // 将新对象放入 cache 中 cache.set(obj, newObj); // 处理特殊对象的情况 if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof RegExp) { return new RegExp(obj); } // 处理循环引用的情况 Object.keys(obj).forEach((key) => { newObj[key] = deepCopy(obj[key], cache); }); return newObj; }; // 测试 Date 和 regExp 的深拷贝: const date = new Date(); const regExp = /test/g; const cloneDate = deepCopy(date); // 深拷贝 const cloneRegExp = deepCopy(regExp); // 深拷贝 console.log(cloneDate === date); // false console.log(cloneRegExp === regExp); // false