浅拷贝定义

自己创建一个新的对象,来接受你要重新复制或者引用的对象值,如果对象属性是基本的数据类型,复制的就是基本的数据类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定也会影响到另一个对象。

常见的浅拷贝方法

1.Object.assign

  1. let target = {}
  2. let source = {a:{b:1}};
  3. Object.assign(target,souce)
  4. console.log(target) //{a:{b:1}}

特性

1.他不会拷贝对象的继承属性

2.他不会拷贝对象的不可枚举属性

3.可以拷贝Symbol类型的属性

2.扩展运算符

  1. let obj = { a: 1, b: { c: 1 } };
  2. let obj2 = { ...obj };
  3. obj.a = 2;
  4. console.log(obj); // {a:2,b:{c:1}}
  5. console.log(obj2); // { a: 1, b: { c: 1 } }
  6. obj.b.c = 2;
  7. console.log(obj); // {a:2,b:{c:2}}
  8. console.log(obj2); //{ a: 1, b: { c: 2 } }
  9. /*数组浅拷贝*/
  10. let arr = [1,2,3]
  11. let newArr = [...arr] //相当于slice

3.concat拷贝数组

  1. let arr = [1,2,3]
  2. let newArr = arr.concat();
  3. newArr[1] = 100
  4. console.log(arr) // [1,2,3]
  5. console.log(newArr) // [1,100,3]

4.slice

  1. let arr = [1,2,{val:4}]
  2. let newArr = arr.slice()
  3. newArr[2].val =1000;
  4. console.log(arr) // [1,2,{val:1000}]

5.手工实现一个浅拷贝

思路:

1.对基础类型做一个最基本的一个拷贝

2.对引用类型开辟一个新的存储,并且拷贝一层对象属性

  1. const shallowClone = (target) => {
  2. if(typeof target === 'object' && target!==null){
  3. const cloneTarget = Array.isArray(target) ? [] : {}
  4. for(let prop in target){
  5. if(target.hasOwnProperty(prop)){
  6. cloneTarget[prop] = target[prop]
  7. }
  8. }
  9. return cloneTarget
  10. }else{
  11. return target
  12. }
  13. }
  14. let obj = { a: 1, b: { c: 1 } };
  15. let obj2 = shallowClone(obj)
  16. obj.b.c = 2;
  17. console.log(obj2)

深拷贝定义

将一个对象从内存中完整的拷贝一份出来给目标对象,并从堆内存中开辟一块全新的空间存放新的对象,且新的对象的修改并不会改变原对象,二者实现真正的分离

1.乞丐版(JSON.stringify)

  1. let obj1 = {a:1,b:[1,2,3]}
  2. let str = JSON.stringify(obj1)
  3. let obj2 = JSON.parse(str)
  4. console.log(obj2) // {a:1,b:[1,2,3]}
  5. obj1.a = 2
  6. obj1.b.push(4)
  7. console.log(obj1) // {a:2,b:[1,2,3,4]}
  8. 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存储的值

简易版:
  1. const deepClone = (target) => {
  2. if (typeof target === "object" && target !== null) {
  3. const cloneTarget = Array.isArray(target) ? [] : {};
  4. for (let prop in target) {
  5. if (target.hasOwnProperty(prop)) {
  6. cloneTarget[prop] = deepClone(target[prop]);
  7. }else{
  8. cloneTarget[prop] = target[prop];
  9. }
  10. }
  11. return cloneTarget;
  12. } else {
  13. return target;
  14. }
  15. };

完善版:
  1. const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
  2. const deepClone = function (obj, hash = new WeakMap()) {
  3. if (obj.constructor === Date)
  4. return new Date(obj) // 日期对象直接返回一个新的日期对象
  5. if (obj.constructor === RegExp)
  6. return new RegExp(obj) //正则对象直接返回一个新的正则对象
  7. //如果循环引用了就用 weakMap 来解决
  8. if (hash.has(obj)) return hash.get(obj)
  9. let allDesc = Object.getOwnPropertyDescriptors(obj)
  10. //遍历传入参数所有键的特性
  11. let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
  12. //继承原型链
  13. hash.set(obj, cloneObj)
  14. for (let key of Reflect.ownKeys(obj)) {
  15. cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
  16. }
  17. return cloneObj
  18. }
  19. // 下面是验证代码
  20. let obj = {
  21. num: 0,
  22. str: '',
  23. boolean: true,
  24. unf: undefined,
  25. nul: null,
  26. obj: { name: '我是一个对象', id: 1 },
  27. arr: [0, 1, 2],
  28. func: function () { console.log('我是一个函数') },
  29. date: new Date(0),
  30. reg: new RegExp('/我是一个正则/ig'),
  31. [Symbol('1')]: 1,
  32. };
  33. Object.defineProperty(obj, 'innumerable', {
  34. enumerable: false, value: '不可枚举属性' }
  35. );
  36. obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
  37. obj.loop = obj // 设置loop成循环引用的属性
  38. let cloneObj = deepClone(obj)
  39. cloneObj.arr.push(4)
  40. console.log('obj', obj)
  41. console.log('cloneObj', cloneObj)

改良版本容易理解一点
  1. function cloneDeep(obj, map = new WeakMap()) {
  2. if (!obj instanceof Object) return obj; // 基本数据
  3. if (obj instanceof Date) return new Date(obj);
  4. if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
  5. if (map.get(obj)) return map.get(obj); // 解决循环引用
  6. if (obj instanceof Function) { // 解决函数
  7. return function () {
  8. return obj.apply(this, [...arguments]);
  9. };
  10. }
  11. const res = new obj.constructor(); // 下面是数组/普通对象/Set/Map 的处理
  12. obj instanceof Object && map.set(obj, res);
  13. if (obj instanceof Map) {
  14. obj.forEach((item, index) => {
  15. res.set(cloneDeep(index, map), cloneDeep(item, map));
  16. });
  17. }
  18. if (obj instanceof Set) {
  19. obj.forEach((item) => {
  20. res.add(cloneDeep(item, map));
  21. });
  22. }
  23. Object.keys(obj).forEach((key) => {
  24. if (obj[key] instanceof Object) {
  25. res[key] = cloneDeep(obj[key], map);
  26. } else {
  27. res[key] = obj[key];
  28. }
  29. });
  30. return res;
  31. }
  32. const map = new Map();
  33. map.set({ a: 1 }, "1");
  34. const source = {
  35. name: "Jack",
  36. meta: {
  37. age: 12,
  38. birth: new Date("1997-10-10"),
  39. ary: [1, 2, { a: 1 }],
  40. say() {
  41. console.log("Hello");
  42. },
  43. map
  44. },
  45. };
  46. source.source = source;
  47. const newObj = cloneDeep(source);
  48. console.log(newObj.meta.ary[2] === source.meta.ary[2]); // false
  49. console.log(newObj.meta.birth === source.meta.birth); // false
  50. console.log(newObj);

小结

浅拷贝:只是创建了一个新的对象,复制了原有对象的基本类型的值

深拷贝:对于复杂引用数据类型,其在堆内存中完全开辟一块内存地址,并将原有的对象完全复制过来存放