【面试题解】JavaScript的深浅拷贝,如何手写深拷贝?

本系列面试题旨在学会相关知识点,从而轻松应对面试题的各种形式,本文讲解了 JavaScript 中拷贝的相关知识,以及如何手写深浅拷贝。

感觉有帮助的小伙伴请点赞👍鼓励一下 ~

什么是拷贝

拷贝其实就是复制,很多场景需要我们复制一份数据出来,然后对复制后的数据进行操作,可能要求不影响原数据,也可能会要求和原数据产生一些联动。所以根据深拷贝和浅拷贝的功能,就可以满足上述两种要求。

值类型的拷贝

值类型其实没有深浅拷贝之分,亦可以说值类型都是深拷贝。因为值类型拷贝后的值,不会跟原数据产生任何联动,修改拷贝后的值,原数据不会产生任何变化。

  let a = 1
  let b = a
  b = 2
  console.log(a) // => 1
  console.log(b) // => 2

浅拷贝

重新在堆中创建内存,拷贝前后的基本类型互不影响,拷贝前后的引用类型还是会共享同一块内存,故而会相互影响。

先写一个例子来看一下:

  // 定义一个浅拷贝函数
  function shallowCopy(obj) {
    const cloneObj = {}
    for (let i in obj) {
      if (obj.hasOwnProperty(i)) {
        cloneObj[i] = obj[i]
      }
    }
    return cloneObj
  }

  // 声明person
  let person = {
    name: "张三",
    hobbies: ["吃饭", "睡觉", "打豆豆"]
  }

  // 对person进行浅拷贝得到person1
  let person1 = shallowCopy(person)

  //修改值类型
  person1.name = '李四'

  //修改引用类型
  person1.hobbies[0] = '美女'

  console.log(person);
  console.log(person1);

可以看到我们修改 person1 的值类型属性 name ,并没有影响到 personname,但是我们修改引用类型 hobbies 的时候,person 也随着 person1 改变了,这就是浅拷贝。

image.png

除了上面这一种,浅拷贝的实现方式还有 Object.assign()展开运算符...array.slice() array.concat()

深拷贝

从堆内存中开辟一块新的区域存放新对象,对原始对象的所有属性进行递归拷贝,对所有的引用类型的属性同样开辟新区域,修改新对象不会影响原对象。

从上面的浅拷贝的例子中可以看出,personhobbies 虽然是个引用类型,但是 hobbies 的每一个元素都是一个字符串,也就是值类型,所以我们只要再次对 hobbies 进行浅拷贝,那么 hobbies 也就会互不影响了。
所以我们可以得出一个结论,只要对一个对象无限递归进行浅拷贝,最终的结果就是一个深拷贝。

递归浅拷贝

代码如下,要考虑到种种特殊情况。

// 定义一个深拷贝函数
  function deepClone(obj) {
    const cloneObj = new obj.constructor()
    if (obj === null) return obj
    if (obj instanceof Date) return new Date(obj)
    if (obj instanceof RegExp) return new RegExp(obj)
    if (typeof obj !== 'object') return obj
    for (let i in obj) {
      if (obj.hasOwnProperty(i)) {
        cloneObj[i] = deepClone(obj[i])
      }
    }
    return cloneObj
  }

  // 声明person
  let person = {
    name: "张三",
    hobbies: ["吃饭", "睡觉", "打豆豆"]
  }

  // 对person进行浅拷贝得到person1
  let person1 = deepClone(person)

  //修改值类型
  person1.name = '李四'

  //修改引用类型
  person1.hobbies[0] = '美女'

  console.log(person);
  console.log(person1);

结果如下,现在只有李四喜欢美女了。说明我们的深拷贝就成功了。

image.png

JSON.parse(JSON.stringify())

除了上面递归浅拷贝的方式来实现深拷贝之外,还可以使用 JSON.parse(JSON.stringify()) 来达到相同的结果。

但是这种方式有它的弊端,我们来看一下

  // 声明person
  let person = {
    name: "张三",
    hobbies: ["吃饭", "睡觉", "打豆豆"],
    date: new Date,
    fuc: () => { },
    reg: /w/
  }

  // 对person进行浅拷贝得到person1
  const person1 = JSON.parse(JSON.stringify(person));


  //修改值类型
  person1.name = '李四'

  //修改引用类型
  person1.hobbies[0] = '美女'

  console.log(person);
  console.log(person1);

来看一下结果,date 属性从一个对象变成了一个字符串,fuc 属性消失了,reg 属性变成了空对象。所以说这种方式弊端是很大的,一不小心就会有意外产生。

  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略;
  • Date 日期会被当做字符串处理;
  • NaNInfinity 格式的数值及 null 都会被当做 null
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性;
  • 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误;

image.png

我们再使用递归浅拷贝的方式来看一下结果,简直是一模一样。

image.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值