如何正确克隆 JavaScript 对象 技术背景 在 JavaScript 中,对象是引用类型。当我们将一个对象赋值给另一个变量时,实际上是复制了对象的引用,而不是对象本身。这意味着对新变量的修改会影响原始对象。在某些情况下,我们需要创建一个对象的副本,使得对副本的修改不会影响原始对象,这就需要进行对象克隆。
实现步骤 浅拷贝 浅拷贝只复制对象的一层属性,如果对象的属性是引用类型,则只复制引用,而不是对象本身。
1. 使用 Object.assign()
1 2 const original = { a : 1 , b : { c : 2 } };const shallowClone = Object .assign ({}, original);
2. 使用扩展运算符(Spread Operator) 1 2 const original = { a : 1 , b : { c : 2 } };const shallowClone = { ...original };
深拷贝 深拷贝会递归地复制对象的所有属性,包括嵌套对象,从而创建一个完全独立的对象副本。
1. 使用 JSON.parse(JSON.stringify())
1 2 const original = { a : 1 , b : { c : 2 } };const deepClone = JSON .parse (JSON .stringify (original));
注意 :该方法有局限性,它会忽略 undefined
、function
、Symbol
等属性,并且不能处理循环引用的对象。
2. 自定义递归函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function clone (obj ) { if (null == obj || "object" != typeof obj) return obj; if (obj instanceof Date ) { const copy = new Date (); copy.setTime (obj.getTime ()); return copy; } if (obj instanceof Array ) { const copy = []; for (let i = 0 , len = obj.length ; i < len; i++) { copy[i] = clone (obj[i]); } return copy; } if (obj instanceof Object ) { const copy = {}; for (const attr in obj) { if (obj.hasOwnProperty (attr)) copy[attr] = clone (obj[attr]); } return copy; } throw new Error ("Unable to copy obj! Its type isn't supported." ); }const original = { a : 1 , b : { c : 2 } };const deepClone = clone (original);
3. 使用 structuredClone()
1 2 const original = { a : 1 , b : { c : 2 } };const deepClone = structuredClone (original);
注意 :structuredClone()
是一个新的 JavaScript 标准,浏览器兼容性需要检查。
使用第三方库 1. jQuery 1 2 3 4 5 const original = { a : 1 , b : { c : 2 } };const shallowClone = jQuery.extend ({}, original);const deepClone = jQuery.extend (true , {}, original);
2. Lodash 1 2 3 4 5 6 const _ = require ('lodash' );const original = { a : 1 , b : { c : 2 } };const shallowClone = _.clone (original);const deepClone = _.cloneDeep (original);
最佳实践 浅拷贝 :如果对象只有一层属性或者不需要对嵌套对象进行独立修改,使用 Object.assign()
或扩展运算符进行浅拷贝,它们简洁高效。深拷贝 :如果对象不包含 undefined
、function
、Symbol
等特殊属性,且没有循环引用,可以使用 JSON.parse(JSON.stringify())
。 如果需要处理复杂对象,包括日期、数组等,自定义递归函数是一个不错的选择。 如果浏览器支持,structuredClone()
是最简单的深拷贝方法。 如果项目中已经使用了 jQuery 或 Lodash 等库,使用它们提供的克隆方法更方便。 常见问题 循环引用问题 使用 JSON.parse(JSON.stringify())
和简单的递归函数会遇到循环引用问题,导致错误。可以使用带有缓存的递归函数来解决,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function cloneWithCache (obj ) { const cache = new WeakMap (); function copy (object ) { if (typeof object !== 'object' || object === null ) return object; if (cache.has (object)) return cache.get (object); let result; if (object instanceof Date ) { result = new Date (object.getTime ()); } else if (object instanceof Array ) { result = []; for (const item of object) { result.push (copy (item)); } } else { result = {}; for (const key in object) { if (object.hasOwnProperty (key)) { result[key] = copy (object[key]); } } } cache.set (object, result); return result; } return copy (obj); }const circularObj = {}; circularObj.self = circularObj;const clonedCircularObj = cloneWithCache (circularObj);
性能问题 JSON.parse(JSON.stringify())
涉及字符串的序列化和反序列化,性能较低,不适合处理大型对象。在性能敏感的场景下,建议使用自定义递归函数或第三方库的克隆方法。