如何正确克隆一个JavaScript对象
技术背景
在JavaScript里,对象属于引用类型。这意味着直接用赋值操作符(=
)来复制对象时,实际上只是复制了对象的引用,而不是对象本身。所以,对新变量进行修改会影响到原对象。因此,我们常常需要克隆对象来获得一个独立的副本。
实现步骤
1. 使用结构化克隆(Structured Cloning)
这是一个新的JS标准,在很多浏览器都能使用。
1
| const clone = structuredClone(object);
|
2. 递归复制
1 2 3 4 5 6 7 8
| function clone(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; }
|
3. 处理特定类型的递归复制
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
| function clone(obj) { var copy;
if (null == obj || "object" != typeof obj) return obj;
if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; }
if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = clone(obj[i]); } return copy; }
if (obj instanceof Object) { copy = {}; for (var 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."); }
|
4. 使用JSON方法
1
| const clone = JSON.parse(JSON.stringify(object));
|
5. 使用Object.assign()
1
| var clone = Object.assign({}, obj);
|
6. 使用扩展运算符(Spread Operator)
7. 使用jQuery
1 2 3 4
| var copiedObject = jQuery.extend({}, originalObject);
var copiedObject = jQuery.extend(true, {}, originalObject);
|
8. 使用Lodash
1
| var y = _.clone(x, true);
|
核心代码
递归复制函数
1 2 3 4 5 6 7 8
| function clone(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; }
|
处理特定类型的递归复制函数
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
| function clone(obj) { var copy;
if (null == obj || "object" != typeof obj) return obj;
if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; }
if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = clone(obj[i]); } return copy; }
if (obj instanceof Object) { copy = {}; for (var 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."); }
|
最佳实践
- 浅拷贝场景:如果对象没有嵌套对象,使用
Object.assign()
或者扩展运算符(...
)会比较方便。
1 2
| let obj = {a: 1, b: 2}; let cloned = {...obj};
|
- 深拷贝场景:若对象有嵌套对象,推荐使用递归复制函数或者
JSON.parse(JSON.stringify())
。不过JSON.parse(JSON.stringify())
不能处理函数、undefined
、RegExp
等特殊类型。
1 2
| let obj = {a: {b: 1}}; let cloned = clone(obj);
|
- 使用第三方库:若需要处理复杂的克隆需求,可考虑使用Lodash或者jQuery。
1
| var y = _.clone(x, true);
|
常见问题
1. 循环引用问题
使用递归复制函数或者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
| function cloneDR(o) { const gdcc = "__getDeepCircularCopy__"; if (o !== Object(o)) { return o; }
var set = gdcc in o, cache = o[gdcc], result; if (set && typeof cache == "function") { return cache(); } o[gdcc] = function() { return result; }; if (o instanceof Array) { result = []; for (var i = 0; i < o.length; i++) { result[i] = cloneDR(o[i]); } } else { result = {}; for (var prop in o) if (prop != gdcc) result[prop] = cloneDR(o[prop]); else if (set) result[prop] = cloneDR(cache); } if (set) { o[gdcc] = cache; } else { delete o[gdcc]; } return result; }
|
2. 特殊类型处理问题
JSON.parse(JSON.stringify())
不能处理函数、undefined
、RegExp
等特殊类型。对于这些特殊类型,需要使用其他方法进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function clone(obj) { if (null == obj || "object" != typeof obj) return obj; if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof RegExp) { return new RegExp(obj); } if (typeof obj === 'function') { return obj.bind({}); } var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); } return copy; }
|