对象浅拷贝与深拷贝是我们经常在项目开发中遇到的问题。那怎么更好的实现呢?
在JavaScript中,浅拷贝(shallow copy)和深拷贝(deep copy)是处理对象和数组拷贝时经常遇到的概念。它们主要区别在于拷贝的深度和对原始对象内部嵌套对象的处理方式。
浅拷贝(Shallow Copy)
浅拷贝是指只复制对象或数组本身及其直接属性或元素,而不复制其内部的嵌套对象或数组。在浅拷贝中,如果原始对象含有引用类型(如对象、数组等)作为属性值,那么拷贝后的对象也会共享这些引用类型的引用。
在JavaScript中,常见的浅拷贝方法包括:
1、Object.assign():
ini
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj);
这种方式会复制obj的直接属性到一个新的空对象中,但是对于嵌套对象b,只是复制了其引用,因此shallowCopy.b和obj.b指向同一个对象。
2、扩展操作符(Spread Operator):
ini
const shallowCopy = { ...obj };
与Object.assign类似,也是进行浅拷贝。
3、Array.prototype.slice():
对于数组,slice方法可以实现浅拷贝,类似于使用扩展操作符。
深拷贝(Deep Copy)
深拷贝指的是创建一个对象的副本,使得副本的所有层次的属性都是独立于原始对象的。这意味着不仅仅是顶层属性被复制,嵌套的对象、数组、和其他引用类型的属性也被递归地复制到新的实例中。
这与浅拷贝相对,浅拷贝只复制对象的顶层属性,如果属性值是引用类型,则复制的是引用,而不是实际的值。
在JavaScript中,实现深拷贝需要考虑到递归地复制所有嵌套对象或数组。一种常见的深拷贝方法是使用递归和JSON对象的序列化与反序列化:
1、JSON.parse与JSON.stringify
ini
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
const obj = { a: 1, b: { c: 2 } };
const deepCopiedObj = deepCopy(obj);
但是需要注意,使用JSON方法进行深拷贝有一些限制,比如无法处理函数、日期函数、正则表达式、undefined、symbol等特殊对象,循环引用也会导致这种方法失败。
2、实现深拷贝
1、深拷贝 基础版本
javascript
// 深拷贝 基础版本
function deepClone(target) {
// 先判断是否为一个对象,如果是,就进行对象的处理流程
if (typeof target === 'object') {
let cloneTarget = {};
// 对属性进行循环遍历
for (const key in target) {
// 递归调用
cloneTarget[key] = deepClone(target[key])
}
return cloneTarget
} else {
return target
}
}
2、深拷贝 优化版本
javascript
// 深拷贝 优化版本
function deepClone2(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 检查哈希表中是否存在拷贝过的对象,处理循环引用
if (hash.has(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj;
}
3、深拷贝 最终版本
javascript
// 深拷贝 最终版本
/**
*
* @param {被克隆的对象} obj
* @param {避免循环引用} hash
* */
function deepClone3(obj, hash = new WeakMap()) {
// 处理边界值
// null undefined 和非对象的情况
if (object === null || typeof obj !== 'object') {
return obj
}
// 对日期的克隆
if (obj instanceof Date) {
return new Date(obj)
}
// 对正则的克隆
if (obj instanceof RegExp) {
return new RegExp(obj)
}
// 避免循环引用
if (hash.has(obj)) {
return hash.get(obj)
}
// 获取属性值 通过Object.getOwnPropertyDescriptors是能够获取symbol等属性值的。
let allDes = Object.getOwnPropertyDescriptors(obj);
// 创建一个新对象,并且继承传入对象的原型链
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDes)
// 处理循环引用
hash.set(obj, cloneObj)
// 使用递归处理,递归克隆每个键对应的值
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = deepClone(obj[key], hash)
}
return cloneObj
}