JS 深拷贝 cloneDeep

1,332 阅读3分钟

深拷贝

解构赋值

只针对一层

let a = {
    name: '盏灯',
    age: 18
}

let b = { ...a }

b.name = '一盏灯'

console.log('a', a)

console.log('b', b)

结果, 确实改变b,a不受影响不会改变,正是我们要的效果。

截屏2023-02-08上午11.54.35.png

再来看看这种es6展开语法多层的情况。

let a = {
    name: '盏灯',
    age: 18,
    father: {
        father: '111'
    }
}

let b = { ...a }

b.name = '一盏灯'

console.log('a', a)
console.log('b', b)

// 改最外面(也就是第一层)没问题,改b不会影响到a

b.father.father = '222'

console.log('a', a)
console.log('b', b)

// 这个时候,当改动b的father的father的时候,a的father的father也改变了。
// 所以这种方法,如果对象只有一层,用这种办法没问题;如果是多层这种方法就🚫

截屏2023-02-08下午2.53.41.png

JSON.parse(JSON.stringify(待拷贝对象))

这个一层,多层都可以拷贝,没问题,而且改变b不会影响a的值

image.png

可以看到改变b的爷爷,a的爷爷是不受影响的。

image.png

但是

如果对象中有函数,那情况就不同了

let a = {
    name: '盏灯',
    age: 18,
    father: {
      father: '111'
    },
    say () {
      console.log('hello world')
    }
}

let b = JSON.parse(JSON.stringify(a))

console.log('a', a)
console.log('b', b)
// 会发现b中没有拷贝到a的方法

image.png

lodash.cloneDeep

lodash的深拷贝

lodash是一个实用且高性能的js实用工具库。

原生html引用cdn方式
<script type="module">

  import lodash from 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/+esm'
  console.log(lodash)
  console.log(lodash.cloneDeep)

  let a = {
    name: '盏灯',
    age: 18,
    father: {
      father: '111'
    },
    say () {
      console.log('hello world')
    }
  }

  let b = lodash.cloneDeep(a)

  b.name = '一盏灯'

  console.log('a', a)
  console.log('b', b)

  b.father.father = '222'

  console.log('a', a)
  console.log('b', b)

</script>
通过npm方式
npm i --save lodash
import _ from 'lodash'

let a = {
    name: '盏灯',
    age: 18,
    father: {
      father: '111'
    },
    say () {
      console.log('hello world')
    }
}

let b = _.cloneDeep(a)

b.father.father = '222'

console.log(a)
console.log(b)

image.png

从截图可以看到

用lodash这个库

1、能多层拷贝
2、改变拷贝过来的b,被拷贝的a不会影响。(完全变地址栈)
3、函数也没问题

那我自己封一个可不可以

可以

1、浅的,一层的,这样写

function clone (target) {
    let cloneTarget = {}
    
    for (const key in target) {
        cloneTarget[key] = otarget[key]
    }
    
    return cloneTarget
}

cloneTarget: 拷贝出来的 target: 被拷贝的

2、不行啊,我要深的。深的,这样写。

function clone (target) {
    if (typeof target === 'object') {
        let cloneTarget = {}
            
        for (const key in target) {
            cloneTarget[key] = clone(target[key])
        }
        
        return cloneTarget
        
    } else {
        return target
    }
}

3、

这个深拷贝还不够,需要考虑以下问题,更进一步去探索

  1. 不可枚举属性 和 Symbol类型 不可复制的问题,用什么来解决。
    答:Reflect.ownKeys()

    Reflect.ownKeys() 方法返回一个由目标对象自身的属性键组成的数组。跟Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)) 得到的一样。

  2. 当参数值为Date、RegExp类型时,直接生成一个新的实例并返回。

  3. 利用Object.getOwnPropertyDescriptors()方法 获得 对象的所有属性以及对应的特性。
    也就是说,这个方法返回给定对象的所有属性的信息,包括有关getter和setter的信息。所有属性都要克隆出来。不能丢。

  4. 使用Object.create()方法创建一个新的对象,并继承传入原对象的原型链。
    Object.create()方法会创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

5、使用WeakMap类型作为Hash表。
WeakMap 是 弱引用类型,可以防止内存泄漏,所以可以用来检测循环引用,如果存在循环,则引用直接返回WeakMap存储的值。

WeakMap 自动垃圾回收,避免内存泄漏

WeakMap的特性就是,保存在其中的对象不会影响垃圾回收,如果WeakMap保存的节点,在其他地方都没有被引用了,那么即使它还在WeakMap中也会被垃圾回收回收掉。

function cloneDeep(entity, cache = new WeakMap()) {
    const referenceTypes = ['Array', 'Object', 'Map', 'Set', 'Date'];
    const entityType = Object.prototype.toString.call(entity);
    if (
      !new RegExp(referenceTypes.join('|')).test(entityType) ||
      entity instanceof WeakMap ||
      entity instanceof WeakSet
    )
      return entity;
    if (cache.has(entity)) {
      return cache.get(entity);
    }
    const c = new entity.constructor();

    if (entity instanceof Map) {
      entity.forEach((value, key) =>
        c.set(cloneDeep(key), cloneDeep(value))
      );
    }
    if (entity instanceof Set) {
      entity.forEach((value) => c.add(cloneDeep(value)));
    }
    if (entity instanceof Date) {
      return new Date(entity);
    }
    cache.set(entity, c);
    return Object.assign(
      c,
      ...Object.keys(entity).map((prop) => {
        // debugger;
        return {
          [prop]: cloneDeep(entity[prop], cache),
        };
      })
    );
}

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天 点击查看活动详情