深拷贝 VS 浅拷贝

深拷贝 VS 浅拷贝

深拷贝:拷贝实例。浅拷贝:拷贝引用。

不知道大家还记不记得高程上对数据类型的定义:
基本数据类型:
布尔类型、字符串类型、数字类型。这些类型属于基本数据类型,它们的值通常都是直接存放在栈中,所以平时我们在给给变量赋值的时候,就是直接将值直接复制给变量,新变量的更改不会影响原变量。
引用类型:
对于对象(包含object、array、date、function等等),这些对象的值都存放在堆中,值的引用存放在栈中。

举个🌰:

let a = 10;
let b = a;//10
b = 20;
console.log(a,b);//10,20

let obj = { a:10,b:20 }
let obj2 = obj1;
obj2.a = 100;
console.log(obj.a)//100

image.png
如图,深拷贝就是在内存中重新开辟一个新的内存,将一个对象从内存中完整的读出来,存放到新的内存中,两者互不影响。

话不多说,先撸为敬。

乞丐版

在业务场景中,我们使用的最多的就是下面的方式,但是这样的方式存在着潜在的风险,对于一下循环嵌套的对象、数组等,无法进行有效的复制。具体请看这一篇文章

JSON.parse(JSON.stringify())

基础版本(一)

function deeClone(obj){
    let res = {}
    for (const key in obj) {
      const element = obj[key];
      res[key] = element
    }
    return res
}

let obj = {
    a:10,
    b:[1,3,4,{ c:20 }],
    d:true
}

console.log(deeClone(obj));//{ a: 10, b: [ 1, 3, 4, { c: 20 } ], d: true }

基础版本(二)

创建一个新对象,遍历目标对象,将原对象上的属性和属性值一次挂载到新对象上。
深拷贝,由于我们不知道原对象会嵌套多少层,所以我们需要使用递归来解决。
如果属性值为基本类型,直接将属性和属性值挂载到新对象上。
如果属性值为对象类型,则递归遍历该属性的属性值,然后挂载到新对象上。

function deepClone(target){
    if(typeof target === 'object'){
        let res = Array.isArray(target) ? [] : {};
        for (const key in target) {
          const element = target[key];
          if(typeof element === 'object'){
            res[key] = deepClone(element)
          }else {
            res[key] = element;
          }
        }
        return res;
    }else {
        return target
    }
}

let obj = {
    a: 1,
    b: {
        c: {
            d: function() {
                console.log(1)
            }
        }
    },
    f:[1,2,3,{ g:function(){} }]
};
let obj1 = deepClone(obj);
obj1.a = 2;
console.log(obj);
console.log(obj1)

基础版本(三)

兼容循环引用。

我们借用上面的函数,去对一个有循环引用的对象进行深拷贝:

let obj = {
    a: 1,
    b: {
        c: {
            d: function() {
                console.log(1)
            }
        }
    },
    f:[1,2,3,{ g:function(){} }]
};
obj.obj = obj;
let obj1 = deepClone(obj);
obj1.a = 2;
console.log(obj);
console.log(obj1)

image.png

如上所示,如果存在对象直接或者间接引用自身的情况,就会出现内存溢出的情况。

为了解决这种情况,我们需要在内存中开辟一个新的空间,来存储当前对象和拷贝对象的关系,当拷贝当前对象的时候,现在存储空间中查找,如果存在,直接返回存储中的值,否则,继续拷贝。

function deepClone3(target, map = new WeakMap()) {
    if (typeof target === 'object') {
        let res = Array.isArray(target) ? [] : {};
        if(map.get(target)){
            return map.get(target)
        }
        map.set(target,res);
        for (const key in target) {
          const element = target[key];
          res[key] = deepClone3(element,map);
        }
        return res;
    } else {
        return target
    }
}

let obj = {
    a: 1,
    b: {
        c: {
            b:100,
            d: function() {
                console.log(1)
            }
        }
    },
    f:[1,2,3,{ g:function(){} }]
}

obj.obj = obj;
let obj1 = deepClone3(obj)
console.log(obj1)

再次执行拷贝:我们可以看到obj.obj值为 Circular,即循环引用的意思。

image.png

Map

JavaScript的对象本质上只能是键值对的集合,且键只能为字符串。为了解决这个问题,在ES6中提出了一种新的数据结构:Map。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
Map的API:

  1. size()
  2. set(key,value)
  3. get(key)
  4. has(key)
  5. delete(key)
  6. clear()

Map的遍历方法:

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

WeakMap

WeakMap是Map的一种变形。但是WeakMap只接受对象作为键(除null外),且WeakMap的键对应的对象,不会被计入垃圾回收机制。
使用WeakMap的优势:它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。
WeakMap的API:

  1. get(key)
  2. set(key,value)
  3. has(key)
  4. delete(key)

兼容其他类型

每一个对象都有对应的toString()方法,如果该方法未被重写,则调用的就是Object.prototype上的toString(),返回的就是’[object type]’,type就是该对象对应的数据类型。
所以,为了防止toString()方法被重写,我们直接调用Object.prototype上的方法,获取数据类型:

//判断类型
function getType(target) {
    return Object.prototype.toString.call(target).slice(8, -1);
}

前面提到过,对应可引用的数据类型,我们要递归遍历;对应不可引用的数据类型,直接复制即可:

//判断是否是可引用的数据类型 
function isRefrenceType(target) {
    let type = typeof target;
    return (target !== null && (type === 'object' || type === 'function'))
}

这里大致对一些数据类型进行划分:

//引用类型
const mapTag = 'Map';

const setTag = 'Set';

const arrayTag = 'Array';

const objectTag = 'Object';


//不可引用类型
const boolTag = 'Boolean';

const dateTag = 'Date';

const errorTag = 'Error';

const numberTag = 'Number';

const regexpTag = 'RegExp';

const stringTag = 'String';

const symbolTag = 'Symbol';

const bufferTag = 'Uint8Array';
//判断类型
function getType(target) {
    return Object.prototype.toString.call(target).slice(8, -1);
}
//判断是否是原始类型类型 
function isRefrenceType(target) {
    let type = typeof target;
    return (target !== null && (type === 'object' || type === 'function'))
}
//获取原型上的方法
function getInit(target) {
    let ClassNames = target.constructor;
    return new ClassNames();
}
//引用类型
const mapTag = 'Map';

const setTag = 'Set';

const arrayTag = 'Array';

const objectTag = 'Object';


//不可引用类型
const boolTag = 'Boolean';

const dateTag = 'Date';

const errorTag = 'Error';

const numberTag = 'Number';

const regexpTag = 'RegExp';

const stringTag = 'String';

const symbolTag = 'Symbol';

const bufferTag = 'Uint8Array';

let deepTag = [mapTag, setTag, arrayTag, objectTag];
function deepClone4(target, map = new WeakMap()) {
    let type = getType(target);
    let isOriginType = isRefrenceType(target);
    if (!isOriginType) {
        return target
    }
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target);
    }

    //防止循环引用
    if (map.get(target)) {
        return map.get(target)
    }
    map.set(target, cloneTarget);

    //如果是mapTag 类型
    if (type === mapTag) {
        console.log(target, cloneTarget, 'target')
        target.forEach((v, key) => {
            cloneTarget.set(key, deepClone4(v, map))
        });
        return cloneTarget;
    }

    //如果是setTag 类型
    if (type === setTag) {
        target.forEach((v) => {
            cloneTarget.add(deepClone4(v, map))
        });
        return cloneTarget;
    }

    //如果是arrayTag 类型
    if (type === arrayTag) {
        target.forEach((v, i) => {
            cloneTarget[i] = deepClone4(v, map)
        });
        return cloneTarget;
    }

    //objectTag 类型
    if (type === objectTag) {
        let array = Object.keys(target);
        array.forEach((i, v) => {
            cloneTarget[i] = deepClone4(target[i], map)
        });
        return cloneTarget;
    }
}

const map = new Map();

map.set('key', 'value');

map.set('name', 'kaka')



const set = new Set();

set.add('11').add('12')

const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2,8],
    empty: null,
    map,
    set
};
target.target = target;
const target1 = deepClone4(target)
target1.a = "a"
console.log('🍎', target)
console.log('🍌', target1)
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!