本文详细解析JavaScript中浅层克隆与深度克隆的核心区别,重点阐述引用值与原始值在内存中的处理差异。通过逐步分析递归逻辑、类型判断及原型链检测,提供完整的深度克隆函数实现代码,并讲解hasOwnProperty在遍历中的关键作用,帮助开发者避免对象引用共享导致的意外修改。
相关知识点
1 相关知识点
- 浅层克隆仅复制栈内存中的引用地址至新变量,新旧变量仍指向堆内存中的同一对象实例,因此修改其中一个对象的内容,另一个也会随之改变。
- 深度克隆则创建一个全新的空对象并分配独立内存空间,将原对象的所有数据逐层复制过去,彻底切断新旧对象之间的引用联系。
- 两者的核心区别在于对引用值的处理方式:浅层克隆后,修改源对象会影响克隆对象;而深度克隆后,两者完全独立,互不影响。需要注意的是,原始值的处理在两种克隆中表现一致。
- 原始值(存储于栈内存stack):包括Number、Boolean(false/true)、String、undefined和null。
- 引用值(存储于堆内存heap):包括Array、Object、function,以及Date、RegExp等复杂类型。
浅层克隆
2 浅层克隆
在浅层克隆操作中,原始值的复制仅涉及数值拷贝,不存在相互影响的问题。然而,对于引用值的复制,由于仅复制了内存地址,新旧对象指向同一空间,导致修改引用值时会出现“你改我改”的现象。
2.1 浅克隆函数
function clone(origin, target) {
var target = target || {};
//容错,即防止用户不传递目标参数。若用户传递了参数就用,若没传则拿一个空对象当目标
for (var prop in origin) {
target[prop] = origin[prop];
}
return target;
}
2.2 运用实例
function clone(origin, target) {
var target = target || {};
for (var prop in origin) {
target[prop] = origin[prop];
}
return target;
}
var obj = {
name: 'abc',
age: '18',
sex: 'male',
card: ['a', 'b', 'c'],
book: {
name: 'ccc',
sbook: {
name: 'aaa'
}
}
};
var newobj = {};
clone(obj, newobj);
运行代码如下:

深度克隆
3 深度克隆
执行深度克隆后,引用值的处理效果与原始值一致,即“你改我不改”。这是因为深度克隆为引用值分配了独立的内存空间,尽管内容相同,但指向不同的地址,使得克隆后的值各自独立,互不干扰。
深克隆步骤分析
3.1 深克隆步骤分析
需要进行深度克隆的对象示例如下:
var obj = {
name: 'abc', // 原始值
age: '18', // 原始值
sex: 'male',// 原始值
card: ['a', 'b', 'c'], // 引用值
book: { // 引用值
name: 'ccc', // 原始值
sbook: { // 引用值
name: 'aaa'// 原始值
}
}
};
var obj1 = {};
(1)首先需要遍历目标对象的所有属性。
实现方法:使用 for (var prop in origin){···} 循环遍历。
for (var prop in origin) {
···
}
(2)依次判断属性值是否为原始值。
实现方法:利用 typeof() 函数。若为原始值,直接拷贝;若为引用值(typeof返回'object'),则进行递归操作。需特别注意 typeof(null) 返回 'object',因此必须排除 null 的情况。
if (origin[prop] !== "null" && typeof(origin[prop]) == 'object') {
···
// 递归
} else {
target[prop] = origin[prop];
}
(3)判断引用值是数组还是普通对象。
实现方法:推荐使用 toString(),也可使用 constructor 或 instanceof。后两者可能涉及原型链继承的细微问题,但在常规场景下影响不大。
var toStr = Object.prototype.toString,
arrStr = "[object Array]";
if (toStr.call(origin[prop]) == arrStr) {
··· // 数组
} else {
··· // 对象
}
(4)根据类型建立相应的空数组或空对象。
实现方法:创建一个新的同名空数组或空对象,并将原对象中的数组或对象视为新的原始对象,将其数据递归拷贝至目标对象的同名空数组/对象中。即递归开始拷贝内部数据,并重复执行第(1)步。递归完成后,继续克隆下一个数据项。
var toStr = Object.prototype.toString,
arrStr = "[object Array]";
if (toStr.call(origin[prop]) == arrStr) {
target[prop] = [];
} else {
target[prop] = {};
}
newobj = {
name: 'abc',
age: '18',
sex: 'male',
card: []
// 建立一个新的同名空数组,并把obj的card数据当成一个原始对象,再次拷贝到obj1的card里面
// 即 递归开始拷贝数组或对象里面的数据,递归执行第(1)步
// 执行完数组card拷贝之后,开始同理拷贝下一个对象book···
}
深克隆函数
3.2 深克隆函数
function deepClone(origin, target) {
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for (var prop in origin) {
if (origin.hasOwnProperty(prop)) {
if (origin[prop] !== "null" && typeof(origin[prop]) == 'object') {
if (toStr.call(origin[prop]) == arrStr) {
target[prop] = [];
} else {
target[prop] = {};
}
deepClone(origin[prop], target[prop]);
} else {
target[prop] = origin[prop];
}
}
}
return target;
}
使用三目运算符简化后的代码如下:
// 使用三目运算符简化后
function deepClone(origin, target) {
var target = (target || {}),
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for (var prop in origin) {
if (origin.hasOwnProperty(prop)) {
if (origin[prop] !== "null" && typeof (origin[prop]) == 'object') {
target[prop] = toStr.call(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop], target[prop]);
} else {
target[prop] = origin[prop];
}
}
}
return target;
}
3.3 运用实例
// 使用三目运算符简化后
function deepClone(origin, target) {
var target = (target || {}),
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for (var prop in origin) {
if (origin.hasOwnProperty(prop)) {
if (origin[prop] !== "null" && typeof (origin[prop]) == 'object') {
target[prop] = toStr.call(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop], target[prop]);
} else {
target[prop] = origin[prop];
}
}
}
return target;
}
运行代码如下:

hasOwnProperty
3.4 hasOwnProperty
hasOwnProperty() 方法返回一个布尔值,用于指示对象自身属性中是否包含指定的属性(即是否存在指定的键)。
语法:obj.hasOwnProperty(prop) 参数:要检测的属性的字符串形式表示的名称,或者Symbol。 返回值:用来判断某个对象是否含有指定的属性的布尔值。 描述:所有继承了Object的对象都会继承到hasOwnProperty方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和in运算符不同,该方法会忽略掉那些从原型链上继承到的属性。 用法: a. 使用hasOwnProperty方法判断属性是否存在 b. 区别自身属性与继承属性 c. 遍历一个对象的所有自身属性 d. 使用hasOwnProperty作为属性名 具体知识点请参考 Object.prototype.hasOwnProperty()
若对象内部定义了原型属性,但在遍历时不希望其显示出来,可以使用对象名.hasOwnProperty(属性名) 来判断是否为自身属性。若是自身属性,返回true;若是原型链上的属性,返回false。实例如下:
var obj = {
name: 'ABC',
age: '18',
sex: 'male',
__proto__: {
heart: 'happy'
}
}
for (var prop in obj) {
// 配套使用,起到一个过滤的作用,不把原型链上的数据弄出来
if (obj.hasOwnProperty(prop)) {
console.log(obj[prop]);// ABC 18 male
}
}
个人笔记,欢迎大家交流探讨!
总结
到此这篇关于JavaScript浅层克隆与深度克隆的文章就介绍到这了,更多相关JavaScript浅层克隆与深度克隆内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!