1 什么是深拷贝和浅拷贝?
深拷贝和浅拷贝都是复制对象时常用的两种方式,区别在于对于嵌套对象的处理,浅拷贝只复制属性的第一层属性,双方修改嵌套对象将会互相影响。深拷贝会递归复制每一层的属性,修改任意一方互不影响。
2 浅拷贝和浅拷贝的实现方法
2.1 浅拷贝的实现方法
1.使用Object.assign()方法,接收两个参数(target, source),将source上的第一层属性复制到target的第一层属性上(不改变原有属性,如果重名将会被覆盖,常用于配置对象的修改)。
// Object.assign
// Object.assign() copies property values from a source object to a target object. For example, consider the following code:
const source = { b: 4, c: {d : 9} };
const shallowCopy = Object.assign({}, source);
shallowCopy.c.d = 10;
console.log(source); // { b: 4, c: { d: 10 } }
2. 使用展开运算符赋值
// use ... (spread operator) for shallow copy
const source = { b: 4, c: {d : 9} };
const shallowCopy = {...source};
shallowCopy.c.d = 10;
console.log(source); // { b: 4, c: { d: 10 } }
3. 对数组可以使用slice或者concat方法
// Array use slice or concat for shallow copy
const source = [1, 2, 3];
const shallowCopy_01 = source.slice();
// or
const shallowCopy_02 = source.concat();
shallowCopy_01[0] = 100;
shallowCopy_02[0] = 100;
console.log(source); // [1, 2, 3]
2.2 深拷贝的实现方法-包含手写实现
方法一:使用递归手写实现,具体实现步骤如下所示:
1. 处理基本类型和引用类型
function deepClone(value){
// 处理基本类型(非引用类型)和null
if(typeof value !== 'object' || value === null){
return value
}
}
2. 处理日期Date对象和正则RegExp对象
// 处理正则对象
function deepClone(value){
// ...上一步代码
// Date对象和正则对象直接通过new的方式创建一个新的对象并返回
if(value instanceof RegExp){
return new RegExp(value)
}
if(value instanceof Date){
return new Date(value)
}
}
3. 处理函数(函数一般不进行深拷贝,闭包所设计的引用过于复杂)
function deepClone(value){
//...前面代码
// 处理函数 直接返回,不做处理
if(typeof value === 'function'){
return value
}
}
4. 初始化拷贝对象
// 初始化拷贝对象
function deepClone(value){
// ...前面代码
// 初始化拷贝对象
const copy = Array.isArray(value) ? [] : {}
}
5. 处理循环引用:使用WeakMap记录拷贝过的对象,防止循环引用
// 处理循环引用
function deepClone(value, cache = new WeakMap()){
//...前面代码
// 如果拷贝过的对象,直接返回
if(cache.get(value)){
return cache.get(value)
}
// 缓存拷贝对象
cache.set(value, copy)
}
6. 递归拷贝对象的属性:使用 Reflect.ownKeys 获取对象的所有属性,包括不可枚举属性和 Symbol 属性,然后递归地拷贝每个属性:
// 递归拷贝对象属性
function deepClone(value, cache = new WeakMap()){
// ...上面的代码
// 递归拷贝对象属性
const keys = Reflect.ownKeys(value)
for(let key of keys){
copy[key] = deepClone(value[key], cache)
}
return copy
}
7. 处理 Map和Set
function deepClone(value, cache = new WeakMap()){
// ...上面代码
// 缓存拷贝对象
cache.set(value, copy)
// 处理Map和Set
if(value instanceof Map){
const copy = new Map()
value.forEach((val, key) => {
copy.set(key, deepClone(val, cache))
})
return copy
}
if(value instanceof Set){
const copy = new Set()
value.forEach(val => {
copy.add(deepClone(val, cache))
})
return copy
}
// ...后续代码
}
手写深拷贝完整代码
function deepClone(value, cache = new WeakMap()){
// 处理基本类型(非引用类型)和null,直接返回即可
if(typeof value !== 'object' || value === null){
return value
}
// Date对象和正则对象直接通过new的方式创建一个新的对象并返回
if(value instanceof RegExp){
return new RegExp(value)
}
if(value instanceof Date){
return new Date(value)
}
// 处理函数 直接返回,不做处理
if(typeof value === 'function'){
return value
}
// 初始化拷贝对象(保留对象原型链)
const copy = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value))
// 如果拷贝过的对象,直接返回
if(cache.get(value)){
return cache.get(value)
}
// 缓存拷贝对象
cache.set(value, copy)
// 处理Map和Set
if(value instanceof Map){
const copy = new Map()
value.forEach((val, key) => {
copy.set(key, deepClone(val, cache))
})
return copy
}
if(value instanceof Set){
const copy = new Set()
value.forEach(val => {
copy.add(deepClone(val, cache))
})
return copy
}
// 递归拷贝对象属性(包含不可枚举属性)
const keys = Reflect.ownKeys(value)
for(let key of keys){
copy[key] = deepClone(value[key], cache)
}
return copy
}
方法二:使用JSON.parse(JSON.stringify(obj)),但是会造成部分数据丢失,且无法处理特殊对象。
// deep copt use Json.parse and Json.stringify
const source = { b: 4, c: {d : 9} };
const deepCopy = JSON.parse(JSON.stringify(source));
深拷贝测试示例
const obj = {
num: 1,
str: 'string',
bool: true,
nullValue: null,
undefinedValue: undefined,
symbol: Symbol('sym'),
date: new Date(),
regExp: /\w+/g,
func: function () { console.log('function'); },
arr: [1, 2, { a: 3 }],
obj: { x: 10, y: { z: 20 } },
map: new Map([['key1', 'value1'], ['key2', { a: 1 }]]),
set: new Set([1, 2, { b: 3 }]),
[Symbol('symbolKey')]: 'symbolValue',
};
obj.circularRef = obj; // 添加循环引用
const clonedObj = deepClone(obj);
console.log(clonedObj);
测试结果如下所示:
3 深拷贝需要注意的问题
实现深拷贝时需要考虑以下问题:
- 循环引用问题:如果对象内部引用自己,不考虑该情况将会导致无限递归。
- 性能损耗:深拷贝大型对象和数组将会浪费大量性能,带来卡顿。
- 考虑特殊对象类型:例如Date RegExp Set Map Function等对象需要特殊处理
- 不可枚举属性和原型链:只会复制对象的可枚举属性
- 某些深拷贝造成的数据丢失:使用JSON.parse(JSON.stringify(obj))时造成undefined Symbol Funcion等类型数据的丢失