深拷贝与浅拷贝实现原理

1.浅拷贝

概念

对数据拷贝的时候只拷贝一层,深层次的只拷贝了地址

代码如下(示例):

const obj = {
  a: 1,
  goods: { name: '鞋', price: 199 }
}
const b = { ...obj }

问题

const obj = {
  a: 1,
  goods: { name: '鞋', price: 199 }
}
const b = { ...obj }
b.goods.name = '球鞋'

我们可以通过代码来看一下,当我们拷贝过数据后,去修改拷贝的数据

会发现通过浅拷贝更深层次的引用类型,如果修改 b.googs,最终 obj.goods 也会跟着修改,是因为在拷贝的时候,我们只是将引用地址拷贝给了 b.goods,也就是说 b.goods 和 ob.goodsj 引用的是同一个对象

也可以用 Object.assign()实现浅拷贝

2.深拷贝

JSON 方法实现深拷贝

我们可以先利用一个简单方式来实现深拷贝

var obj = {
  a: 1,
  c: {
    c1: 1,
    c2: 2
  }
}
var str = JSON.stringify(obj)
var b = JSON.parse(str)
// 修改 obj的属性
obj.c.c1 = 2
console.log(b)

我们先将需要拷贝的代码利用 JSON.stringify 转成字符转,然后再利用

JSON.parse 将字符转转回对象,即完成拷贝

但是拷贝如下数据

var obj = {
  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('唱歌')
  },
  id: 1,
  name: 'andy',
  msg: {
    age: 18
  },
  color: ['pink', 'red']
}

问题:

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

总结:

可以看到,两者都是浅拷贝。 Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。

扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

递归深拷贝

递归实现的思路是什么样的?我们来分析一下

  1. 我们肯定要定义一个方法,那么这个方法最终应该返回一个深拷贝的数据

  2. 既然要返回一个数据,我们首先就要定义一个数据,但是数据是对象还是数组?所以需要判断,如果要拷贝的数据是数组,即定义一个数组,如果是一个对象,即定义一个对象

  3. 方法里面怎么拷贝啊?还是一样的利用 for in 循环,在循环内部,需要判断,如果是类型是简单类型,直接拷贝,如果是引用类型,就需要在一次的将引用类型里面的值取出来

根据上面的逻辑我们处理一下代码

var obj = {
  a: 1,
  c: {
    c1: 1,
    c2: 2
  }
}
function deepCopy(obj) {
  // 判断拷贝的数据是对象还是数组 生成定义的数据
  var copy = Array.isArray(obj) ? [] : {}
  for (key in obj) {
    // 循环的时候如果此项为引用类型,需要 在一次的将引用类型里面的值取出来
    if (typeof obj[key] == 'object') {
      // 再次调用该方法取数据
      copy[key] = deepCopy(obj[key])
    } else {
      copy[key] = obj[key]
    }
  }
  return copy
}
var b = deepCopy(obj)
console.log(b)

但是递归也会遇到上面同样的问题

#数据丢失和异常处理

处理函数 Symbol 正则 Error 等数据类型正常拷贝

// 日期格式
if (obj instanceof Date) {
  return new Date(obj)
}
// Symbol
if (obj instanceof Symbol) {
  return new Symbol(obj)
}
// 函数
if (obj instanceof Function) {
  return new Function(obj)
}
// 正则
if (obj instanceof RegExp) {
  return new RegExp(obj)
}

循环引用问题

数据自己引用自己,此时拷贝就会进入死循环

var obj = {
  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('唱歌')
  },
  id: 1,
  name: 'andy',
  msg: {
    age: 18
  },
  color: ['pink', 'red']
}
obj.obj1 = obj

解决思路:

将每次拷贝的数据进行存储,每次在拷贝之前,先看该数据是否拷贝过,如果拷贝过,直接返回,不在拷贝,如果没有拷贝,对该数据进行拷贝并记录该数据以拷贝

1、使用数组

function cloneDeep3(source, uniqueList) {
  if (!isObject(source)) return source
  if (!uniqueList) uniqueList = [] // 新增代码,初始化数组
  var target = Array.isArray(source) ? [] : {}

  // 要是有 别再循环拷贝了 直接返回 该值
  var uniqueData = find(uniqueList, source)
  if (uniqueData) {
    return uniqueData.target
  }

  // 数据不存在,保存源数据,以及对应的引用
  uniqueList.push({
    source: source,
    target: target
  })
  // =============

  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (isObject(source[key])) {
        target[key] = cloneDeep3(source[key], uniqueList) // 新增代码,传入数组
      } else {
        target[key] = source[key]
      }
    }
  }
  return target
}

// 新增方法,用于查找
function find(arr, item) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i].source === item) {
      return arr[i]
    }
  }
  return null
}
// 用上面测试用例已测试通过
  1. 使用 map 数据:强引用,无法被垃圾回收

    function deepCopy(obj, map = new Map()) {
      if (!isObject(obj)) return
      var newObj = Array.isArray(obj) ? [] : {}
      if (map.get(obj)) {
        // 读取要拷贝的数据
        return map.get(obj) // 要是有 别再循环拷贝了 直接返回 该值
      }
      map.set(obj, newObj) // 存拷贝的数据
      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          if (isObject(obj[key])) {
            newObj[key] = deepCopy5(obj[key], map)
          } else {
            newObj[key] = obj[key]
          }
        }
      }
      return newObj
    }
    

  2. 使用 hash 表:弱引用,可被垃圾回收

    function cloneDeep3(source, hash = new WeakMap()) {
      if (!isObject(source)) return source
      if (hash.has(source)) return hash.get(source) // 读取要拷贝的数据 // 要是有 别再循环拷贝了 直接返回 该值
    
      var target = Array.isArray(source) ? [] : {}
      hash.set(source, target) // 新增代码,哈希表设值
    
      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          if (isObject(source[key])) {
            target[key] = cloneDeep3(source[key], hash) // 新增代码,传入哈希表
          } else {
            target[key] = source[key]
          }
        }
      }
      return target
    }
    

lodash

使用 lodash 实现深拷贝

1.下包

npm i lodash

2.引入

import lodash from 'lodash'

3.实现深拷贝

const obj1 = lodash(obj)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值