在 JavaScript 开发中,数据的复制是一个常见的需求。然而,很多人在使用复制功能时,常常会混淆"深拷贝"和"浅拷贝"的概念,导致代码中出现一些难以察觉的错误。今天,我们就来深入探讨一下深拷贝和浅拷贝的区别。
一、深拷贝与浅拷贝的概念
1. 浅拷贝
浅拷贝是指只复制对象的第一层属性,而不会递归复制对象内部的引用类型属性。换句话说,浅拷贝只复制对象的"表面"属性,如果属性值是一个引用类型(如对象或数组),那么复制的只是引用,而不是引用所指向的实际数据。因此,如果修改了浅拷贝后的对象的引用类型属性,原对象也会受到影响。
2. 深拷贝
深拷贝则是完全复制一个对象,包括对象内部的所有层级。深拷贝会递归地复制对象的每个属性,确保新对象与原对象完全独立,互不影响。这意味着,无论原对象的结构有多复杂,深拷贝后的对象都不会受到原对象的任何影响。
二、浅拷贝的实现方法
1. 直接赋值
直接赋值是最简单的浅拷贝方式,但它并不是真正的拷贝,只是将一个变量的引用赋值给另一个变量。因此,修改任何一个变量都会影响另一个变量。
javascript
var stu = {
name: 'xiejie',
age: 18
};
var stu2 = stu;
stu2.name = "zhangsan";
console.log(stu); // { name: 'zhangsan', age: 18 }
console.log(stu2); // { name: 'zhangsan', age: 18 }
2. Object.assign
方法
Object.assign
方法可以将一个或多个源对象的可枚举属性复制到目标对象中。如果源对象的属性值是引用类型,Object.assign
只会复制引用,而不是引用所指向的实际数据。
javascript
const stu = {
name: 'xiejie',
age: 18,
stuInfo: {
No: 1,
score: 100
}
};
const stu2 = Object.assign({}, stu);
stu2.stuInfo.score = 90;
console.log(stu); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 90 } }
console.log(stu2); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 90 } }
3. ES6 扩展运算符
ES6 的扩展运算符(...
)也可以实现浅拷贝。它的工作原理与 Object.assign
类似,同样只会复制引用类型属性的引用。
javascript
const stu = {
name: 'xiejie',
age: 18,
stuInfo: {
No: 1,
score: 100
}
};
const stu2 = {...stu};
stu2.stuInfo.score = 90;
console.log(stu); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 90 } }
console.log(stu2); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 90 } }
4. 数组的 slice
和 concat
方法
对于数组,slice
和 concat
方法可以实现浅拷贝。这些方法不会修改原数组,而是返回一个新数组。但如果数组中包含引用类型元素,这些方法只会复制引用。
javascript
var arr1 = [1, true, 'Hello', { name: 'xiejie', age: 18 }];
var arr2 = arr1.slice();
arr2[3].age = 19;
console.log(arr1); // [ 1, true, 'Hello', { name: 'xiejie', age: 19 } ]
console.log(arr2); // [ 1, true, 'Hello', { name: 'xiejie', age: 19 } ]
5. jQuery 的 $.extend
方法
在 jQuery 中,$.extend
方法可以实现浅拷贝。如果将第一个参数设置为 false
(默认值),则为浅拷贝。
javascript
const obj = {
name: 'wade',
age: 37,
friend: {
name: 'james',
age: 34
}
};
const cloneObj = {};
$.extend(cloneObj, obj);
obj.friend.name = 'rose';
console.log(obj); // { name: 'wade', age: 37, friend: { name: 'rose', age: 34 } }
console.log(cloneObj); // { name: 'wade', age: 37, friend: { name: 'rose', age: 34 } }
三、深拷贝的实现方法
1. JSON.parse(JSON.stringify)
这是一种广为人知的深拷贝方法。通过将对象转换为 JSON 字符串,再将字符串解析为对象,可以实现深拷贝。但这种方法有一个缺点:它不能处理函数和正则对象。
javascript
const stu = {
name: 'xiejie',
age: 18,
stuInfo: {
No: 1,
score: 100
}
};
const stu2 = JSON.parse(JSON.stringify(stu));
stu2.stuInfo.score = 90;
console.log(stu); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 100 } }
console.log(stu2); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 90 } }
2. jQuery 的 $.extend
方法
在 jQuery 中,$.extend
方法也可以实现深拷贝。只需将第一个参数设置为 true
,即可实现深拷贝。
javascript
const obj = {
name: 'wade',
age: 37,
friend: {
name: 'james',
age: 34
}
};
const cloneObj = {};
$.extend(true, cloneObj, obj);
obj.friend.name = 'rose';
console.log(obj); // { name: 'wade', age: 37, friend: { name: 'rose', age: 34 } }
console.log(cloneObj); // { name: 'wade', age: 37, friend: { name: 'james', age: 34 } }
3. 手写递归方法
如果需要更灵活的深拷贝实现,可以手写递归方法。这种方法可以处理各种复杂的数据结构,包括函数和正则对象。
javascript
function deepClone(target) {
if (typeof target !== 'object' || target === null) {
return target;
}
if (Array.isArray(target)) {
const result = [];
for (const item of target) {
result.push(deepClone(item));
}
return result;
}
if (target.constructor === Date) {
return new Date(target);
}
if (target.constructor === RegExp) {
return new RegExp(target);
}
const result = {};
for (const key in target) {
result[key] = deepClone(target[key]);
}
return result;
}