探索 JavaScript 中的对象拷贝:浅拷贝与深拷贝的区别与应用
前言
当涉及JavaScript中的拷贝时,理解浅拷贝和深拷贝的概念至关重要。JavaScript中的拷贝操作通常涉及复制对象的引用或值,这直接影响到如何处理数据和对象之间的关系。本文将探讨浅拷贝和深拷贝的区别,以及如何使用不同的方法来实现它们,帮助你更好地理解和应用JavaScript中的拷贝技术。
什么是拷贝?
拷贝是指将一个对象的值(或引用)复制到另一个对象中的操作。在 JavaScript 中,拷贝通常涉及基本类型和引用类型的复制。
浅拷贝
浅拷贝会复制对象的基本结构,但是只会复制对象的引用,而不是对象本身。这意味着新对象与原对象共享同一组内部对象。以下是几种常见的浅拷贝方法:
- Object.create(obj) :使用
Object.create()
方法基于现有对象创建一个新对象,新对象的原型链指向原对象。
// 原始对象
const obj = {
name: 'libai',
age: 30
};
// 浅拷贝对象
const shallowCopy = Object.create(obj);
// 修改浅拷贝的值
shallowCopy.age = 31;
// 输出结果
console.log('Original Object:', obj); // { name: 'libailibai', age: 30 }
- Object.assign({}, obj) :使用
Object.assign()
方法将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象。
// 原始对象
const obj = {
name: 'libai',
age: 30
};
// 浅拷贝对象
const shallowCopy = Object.assign({}, obj);
// 修改浅拷贝的值
shallowCopy.age = 31;
// 输出结果
console.log('Original Object:', obj); // { name: 'libai', age: 30 }
console.log('Shallow Copy:', shallowCopy); // { name: 'libai', age: 31 }
- [].concat(arr) :使用数组的
concat()
方法创建一个新数组,将原数组的元素添加到新数组中。
// 原始数组
const arr = [1, 2, 3];
// 浅拷贝数组
const shallowCopy = [].concat(arr);
// 修改浅拷贝的值
shallowCopy[0] = 10;
// 输出结果
console.log('Original Array:', arr); // [1, 2, 3]
console.log('Shallow Copy Array:', shallowCopy); // [10, 2, 3]
- 数组解构赋值
[...arr]
:通过数组解构赋值创建一个新数组,复制原数组的元素到新数组中。
// 原始数组
const arr = [1, 2, 3];
// 浅拷贝数组
const shallowCopy = [...arr];
// 修改浅拷贝的值
shallowCopy[0] = 10;
// 输出结果
console.log('Original Array:', arr); // [1, 2, 3]
console.log('Shallow Copy Array:', shallowCopy); // [10, 2, 3]
- arr.slice() :使用数组的
slice()
方法创建一个新数组,包含原数组的所有元素。
// 原始数组
const arr = [1, 2, 3];
// 浅拷贝数组
const shallowCopy = arr.slice(0);
// 修改浅拷贝的值
shallowCopy[0] = 10;
// 输出结果
console.log('Original Array:', arr); // [1, 2, 3]
console.log('Shallow Copy Array:', shallowCopy); // [10, 2, 3]
- arr.reverse().reverse() :
const arr = [1, 2, 3, 4, 5];
arr.reverse(); // 第一次反转,原数组变为 [5, 4, 3, 2, 1]
arr.reverse(); // 第二次反转,原数组再次变为 [1, 2, 3, 4, 5]
深拷贝
深拷贝是创建一个新对象,复制原对象所有的属性值,包括嵌套的对象引用,而不是仅复制引用。这样新对象和原对象完全独立,修改新对象不会影响原对象。以下是几种深拷贝方法及其特点:
- JSON.parse(JSON.stringify(obj)) :利用
JSON.stringify()
将对象转换为 JSON 字符串,再用JSON.parse()
将 JSON 字符串解析为新的对象。这种方法简单易行,但有几个限制,如无法处理循环引用、BigInt 类型、函数和 Symbol 属性。
// 原始对象
const obj = {
name: 'John',
age: 30,
address: {
city: 'New York',
country: 'USA'
}
};
// 深拷贝对象
const deepCopy = JSON.parse(JSON.stringify(obj));
// 修改深拷贝的值
deepCopy.age = 31;
deepCopy.address.city = 'San Francisco';
// 输出结果
console.log('Original Object:', obj); // { name: 'John', age: 30, address: { city: 'New York', country: 'USA' } }
console.log('Deep Copy:', deepCopy); // { name: 'John', age: 31, address: { city: 'San Francisco', country: 'USA' } }
- 自定义递归方法:可以编写递归函数,深度遍历对象的所有属性,判断属性值是否为对象,如果是则递归调用自身,直到复制完整个对象。这种方法需要小心处理循环引用问题,通常通过记录已经复制过的对象来避免。
// 自定义深拷贝函数
function deepCopy(obj, visited = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (visited.has(obj)) {
return visited.get(obj);
}
let newObj = Array.isArray(obj) ? [] : {};
visited.set(obj, newObj);
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
newObj[key] = deepCopy(obj[key], visited);
}
}
return newObj;
}
// 原始对象
const obj = {
name: 'John',
age: 30,
address: {
city: 'New York',
country: 'USA'
}
};
// 深拷贝对象
const deepCopyObj = deepCopy(obj);
// 修改深拷贝的值
deepCopyObj.age = 31;
deepCopyObj.address.city = 'San Francisco';
// 输出结果
console.log('Original Object:', obj); // { name: 'John', age: 30, address: { city: 'New York', country: 'USA' } }
console.log('Deep Copied Object:', deepCopyObj); // { name: 'John', age: 31, address: { city: 'San Francisco', country: 'USA' } }
- 结构化克隆算法
structuredClone(obj)
:这是浏览器 API 提供的一种深拷贝算法,用于复制对象及其所有属性。它与JSON.parse(JSON.stringify(obj))
类似,但可以处理更多类型的对象,例如循环引用和特殊的 DOM 对象。
// 创建一个对象,包括循环引用和 DOM 对象
const obj = {
message: 'Hello',
innerObj: {
value: 123
}
};
// 创建循环引用
obj.self = obj;
// 创建一个 DOM 对象
const div = document.createElement('div');
div.textContent = 'DOM Element';
// 添加 DOM 对象到对象中
obj.element = div;
// 使用 structuredClone 复制对象
const clonedObj = structuredClone(obj);
// 修改克隆对象的属性
clonedObj.message = 'Modified';
clonedObj.innerObj.value = 456;
// 输出结果
console.log('Original Object:', obj);
console.log('Cloned Object:', clonedObj);
浅拷贝与深拷贝的区别
-
浅拷贝(Shallow Copy) :
- 定义:浅拷贝会创建一个新对象,然后将原始对象的基本数据类型的属性复制到新对象中。对于引用数据类型(如对象或数组),浅拷贝仅复制引用,而不复制引用的对象本身。
- 影响:新对象与原对象共享相同的引用对象。如果修改新对象的引用对象,原对象也会受到影响,因为它们引用同一个内存地址。
常见浅拷贝方法:
Object.assign({}, obj)
Object.create(obj)
Array.prototype.concat()
Array.prototype.slice()
- 扩展运算符
[...obj]
或[...arr]
-
深拷贝(Deep Copy) :
- 定义:深拷贝创建一个完全独立的新对象,同时递归复制所有的引用类型的属性,使得新对象和原对象的所有嵌套对象也完全独立。
- 影响:新对象的修改不会影响原对象,因为它们引用的是不同的内存地址。
常见深拷贝方法:
JSON.parse(JSON.stringify(obj))
(注意该方法有一些限制,如无法处理循环引用、BigInt 类型、函数和 Symbol 属性)- 自定义递归函数实现深度复制
- 结构化克隆算法
structuredClone(obj)
(主要用于复制特殊对象,如 DOM 对象)
小结
在 JavaScript 中,拷贝操作涉及到基本类型和引用类型的处理。浅拷贝复制对象的基本结构,但共享内部对象引用,而深拷贝则创建一个完全独立的新对象,包括所有嵌套对象的复制。常见的浅拷贝方法包括 Object.assign、Object.create、数组的 concat 和 slice 方法,以及使用解构赋值。而深拷贝则可以通过 JSON.parse(JSON.stringify(obj))、自定义递归方法或结构化克隆算法实现,每种方法有其适用场景和限制。深浅拷贝的选择取决于需求,理解它们有助于避免意外的对象引用问题和数据共享风险。