浅拷贝定义
自己创建一个新的对象,来接受你要重新复制或者引用的对象值,如果对象属性是基本的数据类型,复制的就是基本的数据类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定也会影响到另一个对象。
常见的浅拷贝方法
1.Object.assign
let target = {}
let source = {a:{b:1}};
Object.assign(target,souce)
console.log(target) //{a:{b:1}}
特性
1.他不会拷贝对象的继承属性
2.他不会拷贝对象的不可枚举属性
3.可以拷贝Symbol类型的属性
2.扩展运算符
let obj = { a: 1, b: { c: 1 } };
let obj2 = { ...obj };
obj.a = 2;
console.log(obj); // {a:2,b:{c:1}}
console.log(obj2); // { a: 1, b: { c: 1 } }
obj.b.c = 2;
console.log(obj); // {a:2,b:{c:2}}
console.log(obj2); //{ a: 1, b: { c: 2 } }
/*数组浅拷贝*/
let arr = [1,2,3]
let newArr = [...arr] //相当于slice
3.concat拷贝数组
let arr = [1,2,3]
let newArr = arr.concat();
newArr[1] = 100
console.log(arr) // [1,2,3]
console.log(newArr) // [1,100,3]
4.slice
let arr = [1,2,{val:4}]
let newArr = arr.slice()
newArr[2].val =1000;
console.log(arr) // [1,2,{val:1000}]
5.手工实现一个浅拷贝
思路:
1.对基础类型做一个最基本的一个拷贝
2.对引用类型开辟一个新的存储,并且拷贝一层对象属性
const shallowClone = (target) => {
if(typeof target === 'object' && target!==null){
const cloneTarget = Array.isArray(target) ? [] : {}
for(let prop in target){
if(target.hasOwnProperty(prop)){
cloneTarget[prop] = target[prop]
}
}
return cloneTarget
}else{
return target
}
}
let obj = { a: 1, b: { c: 1 } };
let obj2 = shallowClone(obj)
obj.b.c = 2;
console.log(obj2)
深拷贝定义
将一个对象从内存中完整的拷贝一份出来给目标对象,并从堆内存中开辟一块全新的空间存放新的对象,且新的对象的修改并不会改变原对象,二者实现真正的分离
1.乞丐版(JSON.stringify)
let obj1 = {a:1,b:[1,2,3]}
let str = JSON.stringify(obj1)
let obj2 = JSON.parse(str)
console.log(obj2) // {a:1,b:[1,2,3]}
obj1.a = 2
obj1.b.push(4)
console.log(obj1) // {a:2,b:[1,2,3,4]}
console.log(obj2) //{a:1,b:[1,2,3]}
注意:存在问题
1.拷贝的对象的值中如果有函数,undefined、symbol这几种类型,经过JSON.stringify序列化后的字符串中这个键值对会消失
2.拷贝Date引用类型会变成字符串
3.无法拷贝不可枚举的对象
4.无法拷贝对象的原型链
5.拷贝RegExp引用类型会变成空对象
6.对象中含有NaN、Infinity以及-Infinity,JSON序列化的结果会变成null
7.无法拷贝对象的循环引用,即对象成环(obj[key]=obj)
2.手写深拷贝
需要注意几个点
1.针对能够遍历对象的不可枚举类型属性以及Symbol类型我们可以使用Reflect.ownKeys方法
2.当参数为Date、RegExp类型时,则可以直接生成一个实例返回、
3.利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性,顺便结合Object的create方法创建一个新对象,并继承传入原对象的原型链
4.利用WeakMap类型作为Hash表,因为WeakMap是弱引用类型,可以有效的防止内存泄露,作为检测循环引用很有帮助,如果存在循环,则直接返回WeakMap存储的值
简易版:
const deepClone = (target) => {
if (typeof target === "object" && target !== null) {
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop]);
}else{
cloneTarget[prop] = target[prop];
}
}
return cloneTarget;
} else {
return target;
}
};
完善版:
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date)
return new Date(obj) // 日期对象直接返回一个新的日期对象
if (obj.constructor === RegExp)
return new RegExp(obj) //正则对象直接返回一个新的正则对象
//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj)
let allDesc = Object.getOwnPropertyDescriptors(obj)
//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
//继承原型链
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}
// 下面是验证代码
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: { name: '我是一个对象', id: 1 },
arr: [0, 1, 2],
func: function () { console.log('我是一个函数') },
date: new Date(0),
reg: new RegExp('/我是一个正则/ig'),
[Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
enumerable: false, value: '不可枚举属性' }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // 设置loop成循环引用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
改良版本容易理解一点
function cloneDeep(obj, map = new WeakMap()) {
if (!obj instanceof Object) return obj; // 基本数据
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
if (map.get(obj)) return map.get(obj); // 解决循环引用
if (obj instanceof Function) { // 解决函数
return function () {
return obj.apply(this, [...arguments]);
};
}
const res = new obj.constructor(); // 下面是数组/普通对象/Set/Map 的处理
obj instanceof Object && map.set(obj, res);
if (obj instanceof Map) {
obj.forEach((item, index) => {
res.set(cloneDeep(index, map), cloneDeep(item, map));
});
}
if (obj instanceof Set) {
obj.forEach((item) => {
res.add(cloneDeep(item, map));
});
}
Object.keys(obj).forEach((key) => {
if (obj[key] instanceof Object) {
res[key] = cloneDeep(obj[key], map);
} else {
res[key] = obj[key];
}
});
return res;
}
const map = new Map();
map.set({ a: 1 }, "1");
const source = {
name: "Jack",
meta: {
age: 12,
birth: new Date("1997-10-10"),
ary: [1, 2, { a: 1 }],
say() {
console.log("Hello");
},
map
},
};
source.source = source;
const newObj = cloneDeep(source);
console.log(newObj.meta.ary[2] === source.meta.ary[2]); // false
console.log(newObj.meta.birth === source.meta.birth); // false
console.log(newObj);
小结
浅拷贝:只是创建了一个新的对象,复制了原有对象的基本类型的值
深拷贝:对于复杂引用数据类型,其在堆内存中完全开辟一块内存地址,并将原有的对象完全复制过来存放