浅拷贝定义
自己创建一个新的对象,来接受你要重新复制或者引用的对象值,如果对象属性是基本的数据类型,复制的就是基本的数据类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定也会影响到另一个对象。
常见的浅拷贝方法
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] = 100console.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 = 2obj1.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]); // falseconsole.log(newObj.meta.birth === source.meta.birth); // falseconsole.log(newObj);
小结
浅拷贝:只是创建了一个新的对象,复制了原有对象的基本类型的值
深拷贝:对于复杂引用数据类型,其在堆内存中完全开辟一块内存地址,并将原有的对象完全复制过来存放
