什么是深克隆?
浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;
深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变;
我们都知道,JavaScript有六种基本的数据类型。
五种基本的数据类型:string,number,undefined,boolean,null
(在内存中表现为一个值,保存在栈内存中)
一种复杂的数据类型(引用类型):object 【function,array,object】
(在内存中表现为一个指针,保存在堆内存中)
第一种:利用JSON
let obj = {
a: '字符串',
b: 10,
c: null,
d: undefined,
e: () => {
console.log('函数')
},
f: Symbol(),
g: new Date(),
h: /^(0|[1-9][0-9]*)$/,
i: NaN,
j: window.Infinity
}
console.log(JSON.parse(JSON.stringify(obj)))
运行结果为:
// a: "字符串"
// b: 10
// c: null
// g: "2022-06-16T12:22:59.703Z"
// h: {}
// i: null
// j: null
由此可以看出,使用 JSON 实现深拷贝会存在一定缺陷
1、属性值为 undefined、函数、Symbol() 的属性会丢失
2、属性值为 时间对象 的会变成时间字符串
3、属性值为 正则表达式 的会变成 {}
4、NaN,无穷数 会被转为 null
5、无法拷贝对象原型链上的 属性 和 方法
6、当数据层级很深的时候,会栈溢出
第二种:扩展运算符
let obj = {
a: '字符串',
b: 10,
c: null,
d: undefined,
e: () => {
console.log('函数')
},
f: Symbol(),
g: new Date(),
h: /^(0|[1-9][0-9]*)$/,
i: NaN,
j: window.Infinity,
k: {
l: 1
}
}
let newObj = {...obj};
newObj.k.l = 2;
console.log(obj)
运行结果为:
// a: "字符串"
// b: 10
// c: null
// d: undefined
// e: () => { console.log('函数') }
// f: Symbol()
// g: Thu Jun 16 2022 21:05:19 GMT+0800 (中国标准时间) {}
// h: /^(0|[1-9][0-9]*)$/
// i: NaN
// j: Infinity
// k: {l: 2}
由此可以看出,obj.k.l 的值已经被改变了,因此使用 扩展运算符 实现深克隆存在的缺陷是 `无法嵌套拷贝`(对多层嵌套数据无效)
第三种:递归完成深克隆
/**
* 定义一个深拷贝函数 接收目标 target 参数
* @param {Object} target
*/
function deepClone(target) {
// 定义一个变量
let result;
// 获取数据类型
let targetType = Object.prototype.toString.call(target);
// 如果是一个数组
if(targetType == '[object Array]'){
result = [];
for (let i = 0; i < target.length; i++) {
// 递归克隆数组中的每一项
// result.push(deepClone(target[i]));
result[i] = deepClone(target[i]);
}
}
// 如果是一个对象
else if(targetType == '[object Object]'){
result = {};
for (let key in target) {
// 递归克隆对象中的每一项
result[key] = deepClone(target[key]);
}
}
// 基本数据类型
else {
result = target;
}
// 返回最终结果
return result
}
/**
* 定义一个深拷贝函数 接收目标 target 参数
* @param {Object} target
*/
deepClone(target) {
// 定义一个变量
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === 'object') {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将 result 赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(deepClone(target[i]))
}
// 判断如果当前的值是 null 的话,直接赋值为 null
}else if(target === null) {
result = null;
// 判断如果当前的值是一个 RegExp 对象的话,直接赋值
}else if(target.constructor === RegExp){
result = target;
}else{
// 否则是普通对象,直接 for in 循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
}
发现一个最新技术
function deepClone(target){
return new Promise((resolve, reject) => {
const { port1, port2 } = new MessageChannel();
port1.postMessage(target);
port2.onmessage = msg => {
resolve(msg.data);
}
})
}
// 请记住 `MessageChannel` 是异步的这个前提!
deepCopy(obj).then(val => {
console.log(val)
});
对数组深拷贝
使用 slice(0) 复制数组
let callbacks = [1, 2, 3, 4, 5];
const copies = callbacks.slice(0)