对象深复制
复制逻辑
什么是深复制和浅复制?JavaScript有6种基本数据类型(Number,Boolean,String、null、udefined和Symbol)和1种引用类型(Object)。
- 基本数据类型自身变量所指的内存,存的就是自身的值(String例外)
- 引用数据类型存的是个引用地址(指针),具体数据存在堆中。
- String在JavaScript比较特殊,它虽然是基本数据类型,但它却是按照引用类型在使用。 对于基本值类型(String)除外,直接复制,要想实现深复制,只要遇到引用类型,必须用new的方式重新创建,并且被复制对象的内部属性,如果是引用,也要这么处理。对象的属性有很多层,这就是深复制实现复杂的原因。
代码实现
首先我们创建一个对象来进行测试
var d = Symbol();
var e = Symbol();
var ss = { a: 1 };
var obj = {
a: 1,
b: 2,
c: [1, 2, 3],
zz: new Set([1, 2, ss]),
yy: new Map(),
[d]: "aaa",
z: document.createElement("div"),
d: {
e: new Date(),
f: /a/g,
g: function (s) {
console.log(s);
},
h: {},
},
};
Object.defineProperties(obj.d.h, {
i: {
value: 10,
},
j: {
configurable: true,
writable: true,
value: [1, 2, 3, 4],
},
k: {
writable: true,
value: {
l: {},
m: "abcde",
n: true,
o: [1, 2, 3],
},
},
[e]: {
value: ["a", "b", "c", "e"],
},
});
Object.defineProperties(obj.d.h.k.l, {
p: {
value: function () {
console.log("p");
},
},
q: {
value: {
r: { a: 1 },
j: { b: 2 },
},
},
});
var a_1 = { a: 1 };
var a_2 = { b: 2 };
obj.yy.set("name", "xietian");
obj.yy.set(a_1, a_2);
//对象深复制
const cloneObject = (source, target) => {
if (source === null || source === undefined) return source; //判断非空非undefined
// if (source === document) return; //非document
if (!Object.prototype.isPrototypeOf(target)) { //target是否已经存在对象原型上
if (HTMLElement.prototype.isPrototypeOf(source)) { //HTML复制
target = document.createElement(source.nodeName);
} else if (source.constructor === RegExp) { //RegExp复制
target = new RegExp(source.source, source.flags);
} else if (source.constructor === Date) { //Date复制
target = new Date(source);
} else if (source.constructor === Function) { //Function 复制
var arr = source.toString().replace(/\n|\r/g, "").trim().match(/\((.*?)\)\s*\{(.*)\}/).slice(1); //进行source处理
target = new Function(arr[0].trim(), arr[1]);
} else if (source.constructor === Set) { //Set复制
target = new Set(cloneObject(Array.from(source.values())))
} else if (source.constructor === Map) { // Map复制
target = new Map();
// entries() 方法返回一个数组的迭代对象,该对象包含数组的键值对 (key/value)。
// 迭代对象中数组的索引值作为 key,数组元素作为 value。
for (var [key, value] of source.entries()) { //解构赋值
if (Object.prototype.isPrototypeOf(key)) { //判断Map中的key和value类型进行后续复制处理 引用类型继续clone
if (Object.prototype.isPrototypeOf(value)) {
target.set(cloneObject(key), cloneObject(value));
} else {
target.set(cloneObject(key), value);
}
} else {
if (Object.prototype.isPrototypeOf(value)) {
target.set(key, cloneObject(value));
} else {
target.set(key, value);
}
}
}
} else {
target = new source.constructor(); //判断完毕后 剩下的数组和对象等 直接new一个source类型
}
}
//getOwnPropertyNames方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。getOwnPropertySymbols返回symbol的值
var names = Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source));
for (var i = 0; i < names.length; i++) {
// console.log(names[i]);
if (source.constructor === Function && names[i] === "prototype") continue; //函数会比较特殊 在这进行判断函数
var desc = Object.getOwnPropertyDescriptor(source, names[i]);
if (Object.prototype.isPrototypeOf(desc.value)) { //函数会因为它的prototype constructor指向的是一个函数 然后函数继续指向函数 所以会死循环
// 根据需要将源对象的描述内容设置给当前目标对象
// 相同属性名的描述内容,及值赋为刚才创建相同类型的对象
Object.defineProperty(target, names[i], {
enumerable: desc.enumerable,
configurable: desc.configurable,
writable: desc.writable,
value: cloneObject(desc.value)
});
} else {
// 如果描述的对象的值不是引用类型,
// 直接将描述对象设置给目标对象的这个属性
Object.defineProperty(target, names[i], desc);
}
}
return target;
}
// a_1.a = 100;
// obj.d.h.j[1] = 1000;
const _obj = cloneObject(obj);
console.log(_obj);