深拷贝和浅拷贝

133 阅读3分钟

所谓拷贝就是赋值。把一个变量赋给另外一个变量,就是把变量的内容进行拷贝;把一个对象的值赋给另一个对象就是把一个对象拷贝一份。这两种拷贝方式常用于拷贝对象

为什么会有深拷贝和浅拷贝之分,就是因为我们通常会进行拷贝的值类型分为两种,一种是基本数据类型,一种是复杂数据类型。

拷贝基本数据(浅拷贝):

基本数据类型也称为值类型。变量对应的内存空间放的是一个值,比如 let a = 100,a这个变量对应的内存地址存放的值就是100,一个确切的值。 所以当我们需要拷贝基本数据类型时,可以直接将变量对应的内存地址的值复制一份,开辟一个新的空间存放,不会和以前的内存空间产生冲突,这种拷贝方式就是浅拷贝

常用es6种的扩展运算符和Object.assign()进行浅拷贝

    let obj = {
    name:'zs',
    age:18,
    }
    // 使用扩展运算符浅拷贝
    let newObj = {...obj}
    
    //使用Object.assign()
    let newObj1 = Object.assign({},obj)
    
拷贝复杂数据类型(深拷贝):
    1. 复杂数据类型就是地址类型,也叫引用类型。变量对应的内存空间存放的是一个地址, 地址对应的内存空间存放的才是真正的数据。就好比我们填写简历是要求写居住地址,我们并不会把我们整个家搬来放在简历上,只会填写对应的地址,根据地址就能找到真正居住的地方。
    1. 因为复杂数据类型本身变量对应的内存空间存放的就是地址,如果使用浅拷贝的方式,拷贝过去的依然是地址,这是就会出现两个变量公用一个地址的数据,只要有一个变量修改了这个地址里的值,另一个变量也会收到影响,这种现象并不是我们想要的,所以此时我们对于这种复杂数据类型的拷贝就需要用到深拷贝

实现深拷贝的几种方法:

  1. 使用JSON对象的方式
let Obj = {
        name: 'zs',
        age: 20,
        hobby: ['打游戏', '看动漫', '哈啤酒'],
        father: {
          name: 'ls',
          age: 45,
          hobby: ['搓麻将', '斗地主'],
        },
        a: /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/,
        b: new Date(),
        c: null,
        d: Symbol(1),
        sing: function () {
          console.log('唱歌')
        },
      }
      window.localStorage.setItem('obj', JSON.stringify(Obj))
      let newObj = JSON.parse(window.localStorage.getItem('obj'))
      console.log(newObj)
     { 
       a: {}
       age: 20
       b: "2022-07-25T07:13:24.336Z"
       c: null
       father: {name: "ls", age: 45, hobby: ["搓麻将", "斗地主"]}
       hobby: ["打游戏", "看动漫", "哈啤酒"]
       name: "zs"
       }

此时我们不难发现,使用JSON进行深拷贝有风险

  • 造成数据丢失和数据异常
  • function、undefined 直接丢失
  • NaN、Infinity 和-Infinity 变成 null
  • RegExpError对象只得到空对象;

2.利用递归进行深拷贝

 let Obj = {
              name: 'zs',
              age: 20,
              hobby: ['打游戏', '看动漫', '哈啤酒'],
              father: {
                name: 'ls',
                age: 45,
                hobby: ['搓麻将', '斗地主'],
              },
              a: /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/,
              b: new Date(),
              c: null,
              d: Symbol(1),
              sing: function () {
                console.log('唱歌')
              },
            }
            
 function deepCopy(obj) {
        let newObj = Array.isArray(obj) ? [] : {}
        for (let key in obj) {
          if (obj[key] instanceof Array) {
            newObj[key] = []
            newObj[key] = deepCopy(obj[key])
          } else if (obj[key] instanceof Object) {
            newObj[key] = {}
            newObj[key] = deepCopy(obj[key])
          } else {
            newObj[key] = obj[key]
          }
        }
        return newObj
      }
      consoloe.log(Obj)
      {a: {}
      age: 20
      b: {}
      c: null
      d: Symbol(1)
      father: {name: 'ls', age: 45, hobby: Array(2)}
      hobby: (3) ['打游戏', '看动漫', '哈啤酒']
      name: "zs"
      sing: {}}

我们不难看出递归也存在上述问题,也可能出现数据异常和数据丢失,并且当要拷贝的对象存在循环引用,也就是对象的某个属性值是这个对象,就会出现死循环,堆栈溢出的问题。
解决思路:
将每次拷贝的数据进行存储,每次在拷贝之前,先看该数据是否拷贝过,如果拷贝过,直接返回,不在拷贝,如果没有拷贝,对该数据进行拷贝并记录该数据以拷贝 可以使用数组,或者new Map()developer.mozilla.org/zh-CN/docs/…

在实际开发中我们常用使用 lodash(www.lodashjs.com/) 实现深拷贝