如何实现深拷贝?使用JSON.parse(JSON.stringify(obj)).有什么缺陷? 啊额矣那啥。。。

1,142 阅读4分钟

前言

earning loads of money image.png

👹面试官问:如何实现深拷贝?
🙉答:使用JSON.parse(JSON.stringify(obj))可以实现深拷贝
👹面试官再问:这个方法有什么缺陷吗?
🙉答:。。。啊额矣那啥。。。

有没有经常遇到这样的问题,面试官问你个问题,你回答了,然后他在根据你的回答在继续问,然后我们就开始啊噢矣呜额了哈哈哈- 🙉

一、浅拷贝

只是将数据中所有的数据引用下来,依旧指向同一个存放地址,拷贝之后的数据修改之后,也会影响到原数据的中的对象数据。例如:Object.assign(),...扩展运算符,或者直接复制。

浅拷贝复制的方法对比

一般作为临时变量使用的时候,可以用到浅拷贝

const obj = { a: 1 };
const copy1 = Object.assign({}, obj);
const copy2 = obj
const copy3 = { ...obj }
console.log(copy1); // { a: 1 }
console.log(copy2); // { a: 1 }
console.log(copy3) // { a: 1 }

(1)使用Object.assign()

const copy1 = Object.assign({}, obj);

const obj = {
  a: 1,
  innerObj: { b: 2 }
};
const copy = Object.assign({}, obj);
obj.a = 10 //  原对象改变 ,不影响拷贝对象的第一层属性值,
obj.innerObj.b = 20  // 源值是一个对象的引用,它仅仅会复制其引用值,只拷贝可枚举的属性值
console.log(copy);
console.log(obj)
//输出
// { a: 1, innerObj: { b: 20 } }
// { a: 10, innerObj: { b: 20 } }

🍉介绍一下Object.assign() 不能做深层拷贝, 对象里面的

Object.assign()  方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

🍈使用

Object.assign(target, ...sources)

接收一个目标对象target多个目标对象, ,结果返回目标读写

🍇使用Object.assingn()合并对象

注意像下面代码那样的话,目标对象自身也会改变。如果不改变原对象,可以这样Object.assign({},o1, o2, o3)用一个空对象作为目标对象对象。

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。

(2)使用直接复制的方式

const copy = obj都是引用地址的指向

可查看这部分第三点js对象引用类型介绍

const obj = {
  a: 1,
  innerObj: { b: 2 }
};
const copy = obj
obj.a = 10 //  指向引用地址
obj.innerObj.b = 20  // 指向引用地址
console.log(copy); // 引用地址指向的a 指向10 它的a也变成10
console.log(obj)
//输出
// { a: 10, innerObj: { b: 20 } }
// { a: 10, innerObj: { b: 20 } }

(3)使用...扩展运算符

const copy = { ...obj }Object.assign() 一样只是对第一层值做了值拷贝,遇到对象里面的对就是引用地址。

const obj = {
  a: 1,
  innerObj: { b: 2 }
};
const copy = { ...obj }
obj.a = 10 //  指向引用地址
obj.innerObj.b = 20  // 指向引用地址
console.log(copy); // 引用地址指向的a 指向10 它的a也变成10
console.log(obj)
//输出
// { a: 1, innerObj: { b: 20 } }
// { a: 10, innerObj: { b: 20 } }

(4)参数可能包含函数、正则、日期、ES6新对象

const _shallowClone = target => {
  let copyShallow = Array.isArray(target) ? [] : {}
  for(let key in target){
      copyShallow[key] = target[key]
  }
  return copyShallow
}

二、深拷贝

将数据中所有的数据拷贝下来,对拷贝之后的数据进行修改不会影响到原数据。

深拷贝的几种方式

(1)JSON.parse(JSON.stringify(obj))

// 不支持值为undefined、函数和循环引用的情况
const cloneObj = JSON.parse(JSON.stringify(obj))

JSON.parse(JSON.stringify(obj))深拷贝的缺陷

1、如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
3、如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
5、JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
6、如果对象中存在循环引用的情况也无法正确实现深拷贝。

(2)递归方式深拷贝

function deepClone(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)
  
  if (cache.has(obj)) return cache.get(obj) // 如果出现循环引用,则返回缓存的对象,防止递归进入死循环
  let cloneObj = new obj.constructor() // 使用对象所属的构造函数创建一个新对象
  cache.set(obj, cloneObj) // 缓存对象,用于循环引用的情况

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], cache) // 递归拷贝
    }
  }
  return cloneObj
}

// 测试
const obj = { name: 'Jack', address: { x: 100, y: 200 } }
obj.a = obj // 循环引用
const newObj = deepClone(obj)
console.log(newObj.address === obj.address) // false

(3)正则匹配判断的递归方式深拷贝

const DeepClone = (target, map = new Map()) => {
                // 参数如果为空
                if(target === null) return target
                // 参数如果不是对象类型,而是基本数据类型
                if(typeof target !== 'object') return target
                const cons = target.constructor
                // 参数为其他数据类型
                if(/^(Function | RegExp | Date | Map | Set)$/i.test(cons)){
                    return new cons(target)
                }
                const cloneTarget = Array.isArray(target)? []:{}
               // 如果存在循环引用,直接返回当前循环引用的值,否则,将其加入map
                if(map.get(target)) return map.get(target)
                map.set(target,cloneTarget)
                for(let key in target){
                    cloneTarget[key] = DeepClone(target[key],map)
                }
                return cloneTarget
            }
        </script>

lagom-freelance-girl-working-at-home-next-to-her-dog.png

推荐文章

学习算法
leetcode hot

参考文章

前端面试题第三点Js 对象引用类型介绍

JSON.parse(JSON.stringify(obj))实现深拷贝的弊端

拷贝详解

手写代码