/*
对象深复制 参数
source 源对象 需要复制的对象
target 目标对象 可以传入已有的对象,也可以由函数返回创建的新对象
return
返回目标对象
对象深复制,可以复制对象中数值、字符、布尔值、undefined、null、object,
数组,set,map,函数,DOM对象,Date,正则的值,可以复制字符属性名和symbol属性名
的内容,可以对对象的属性原有值描述类型进行复制(包括不可删除,不可枚举,只读)。
*/
function cloneObject(source,target={}){
// 创建一个用来判断复制传入参数的类型列表
var ClassList=[Set,Map,Date,RegExp];
// 获取源对象中所有字符串属性名和symbol属性名的数组
var keys=Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source));
// 遍历这个属性名数组
for(var i=0;i<keys.length;i++){
// 如果是函数,函数因为特殊,包含一个prototype属性,这个属性下会引用当前函数自身,就会造成不断迭代进入死循环,
// 因此如果是该属性时,跳出当前循环继续下一次
if(keys[i]==="prototype") continue;
// 获取当前源对象的所有属性名对应的值得描述对象,使用这个可以对复制后的属性也保留这种设置(包括不可删除,不可枚举,只读)
var desc=Object.getOwnPropertyDescriptor(source,keys[i]);
// 如果当前属性名对应的属性值继承与Object,也就是引用类型时,创建新对象并完成递归深复制
// desc.value 就是对象的这个属性名对应的值(key value中的value)
if(desc.value instanceof Object){
var o;
// 新建对象
// 如果这个值是DOM元素,判断值是否继承HTMLElement,就是DOM对象
if(desc.value instanceof HTMLElement){
// DOM克隆
o=desc.value.cloneNode(true);
// 判断当前值得类型是不是类型列表中的其中一个,包括Set,Map,Date,RegExp
}else if(ClassList.includes(desc.value.constructor)){
// 如果是这四种,通过new 类型(对象)可以完成对于原对象的复制
o=new desc.value.constructor(desc.value);
// 如果当前值是函数
}else if(desc.value.constructor===Function){
//将函数转为数组,并且将换行删除,然后用正则选择函数参数部分和函数{}内所有内容,参数为数组的第0项,函数的内容是数组的第1项
var arr=desc.value.toString().replace(/\n/g,"").match(/function\s*\((.*?)\)\s*\{(.*)\}/).slice(1);
// 创建函数,并且将参数和内容传入
o=new Function(arr[0],arr[1]);
}else{
// 剩余所有类型,都通过new 类型() 的方式来创建这个类型的新对象
o=new desc.value.constructor();
}
// 将这个属性的描述对象说明描述项设置给该属性,并且将值设为刚才创建的新对象
Object.defineProperty(target,keys[i],{
value:o,
configurable:desc.configurable,
writable:desc.writable,
enumerable:desc.enumerable
})
// 完成递归,将当前值作为源对象,新创建的对象作为目标对象,递归继续深入复制,因为o对象是引用关系,所以深复制后,这个属性也会改变
cloneObject(desc.value,o);
}else{
// 如果这个属性值不是引用类型,则将这个属性值的描述对象和值设置给这个目标对象对应的值
// 将value设置给目标对象的key
Object.defineProperty(target,keys[i],desc);
}
}
return target;
}