本文详细解析 JavaScript 中对象克隆的机制,区分浅层克隆与深层克隆的核心差异。通过对比引用值处理逻辑,提供基于 for-in 遍历的浅层克隆及利用递归实现深层克隆的代码示例,并展示如何在原型链上扩展 cloneTo 方法以支持链式调用,帮助开发者避免修改克隆对象时影响原数据的常见问题。
JavaScript 深层克隆对象详解及实例
JavaScript 深层克隆对象详解及实例
项目背景与复习动机
在项目开发中,有时会遇到需要深层克隆对象的需求,并且要求基于原型链进行编程。借此机会,我复习了相关知识点,并整理了对应的资料。
对象克隆的基本概念
所谓克隆对象,实际上就是复制一个与原对象完全一致的新对象。初学者可能会觉得这很简单,但实际操作中往往存在陷阱。
var obj1 = {name: 'payen'};
var obj2 = obj1;
简单的赋值操作并非真正的克隆。例如,obj1 和 obj2 仅仅指向同一个对象,它们共享同一份数据。
这两个变量指向相同的内存地址,就像拿到了同一把钥匙,打开的是同一间房子。
这是因为在 JavaScript 中,对象属于引用类型值。
提到引用类型值,
JavaScript 中的引用类型主要指对象。
需要注意的是,数组是特殊的对象,函数也是特殊的可执行对象,它们本质上都是对象。深层克隆的目标是不共享内存空间,即复制出一间全新的房子。相比之下,浅层克隆只是复制了引用指针,而深层克隆则需要递归地复制引用值所指向的内容。
首先我们来了解浅层克隆的实现方式。
浅层克隆的实现与局限
var house = {
name: 'payen',
people: {
father: true,
mother: true
}
}
function easyClone(obj){
var newObj = {};
for(var prop in obj){
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
var newHouse = easyClone(house);
这里使用 easy 作为函数名,虽然不太规范,但便于理解。关于 for-in 循环的性能问题,有兴趣的读者可以参考其他相关文章。
这段代码逻辑简单,主要涉及遍历和赋值操作,此处不再赘述。
查看 Chrome 浏览器的控制台输出。

从结果来看,浅层克隆似乎工作正常。
现在进行一个测试操作。
向新对象中添加一个新的属性或成员。

结果显示,原始对象也随之发生了改变。这说明所谓的“新房子”并没有独立,变量名的改变并未带来数据的隔离。因此,当对象包含引用类型值时,浅层克隆无法保证数据独立性。
那么如何解决这个问题呢?
要获得真正独立的新对象,需要创建一个新对象,并将旧对象内部的属性逐一拷贝到新对象中。
如果对象内部还嵌套了其他对象,该如何处理?
这就需要重复创建和添加的过程,显然这是一个循环处理的过程。
循环实现主要有两种方式。
迭代方式。
递归方式。
相比之下,递归方式更为高效和直观。
在递归循环中,当遇到满足终止条件的情况时逐层返回以结束递归。通过递归逐层查找引用值,直到没有嵌套的引用值为止。具体实现代码如下。
深层克隆的递归实现
var house = {
name: 'payen',
people: {
father: true,
mother: true,
child: {
age: 1
}
},
money: [1000,2000,3000]
}
function deepClone(original, target){
var target = target || {};// 如果target为undefined或没传参,设置空对象
for(var prop in original){// 遍历原对象
if(original.hasOwnProperty(prop)){// 只拷贝对象内部,不考虑原型链
if(typeof original[prop] === 'object'){// 引用值
if(Object.prototype.toString.call(original[prop]) === '[object Array]'){
target[prop] = [];// 处理数组引用值
}else{
target[prop] = {};// 处理对象引用值
}// 可以用三目运算符
deepClone(original[prop],target[prop]);// 递归克隆
}else{// 基本值
target[prop] = original[prop];
}
}
}
return target;
}
var newHouse = deepClone(house);
上述代码中的 if-else 结构虽然可以用三目运算符简化,但为了可读性未做过度压缩。为了验证深层克隆的效果,特意将原始对象结构复杂化(此处暂不考虑函数的深层克隆,因其复杂且意义有限)。现在,这个新对象确实是独立的“新房子”。

此处不再展开更多细节。
从结果可以看出,修改新对象中的引用值,原始对象并未受到影响,证明深层克隆成功。
在原型链上实现克隆的逻辑与此类似,只需将方法挂载到 Object.prototype 上即可。
原型链上的克隆方法扩展
var house = {
name: 'payen',
people: {
father: true,
mother: true,
child: {
age: 1
}
},
money: [1000,2000,3000]
}
Object.prototype.cloneTo = function(obj){
var obj = obj || {};
for(var prop in this){
if(this.hasOwnProperty(prop)){
if(typeof this[prop] === 'object'){
if(Object.prototype.toString.call(this[prop]) === '[object Array]'){
obj[prop] = [];
}else{
obj[prop] = {};
}
this[prop].cloneTo(obj[prop]);
}else{
obj[prop] = this[prop];
}
}
}
return obj;
}
var newHouse = {};
house.cloneTo(newHouse);
感谢阅读,希望本文能对大家有所帮助,谢谢支持!