深浅拷贝学习整理

161 阅读4分钟

深浅拷贝整理

1.浅拷贝的原理和实现

浅拷贝在进行复制时如果是基础数据类型就把基础数据类型的值给新对象,如果是引用数据类型,给新对象的值是存在栈中的地址,如果改变了新对象中的引用数据,也会影响到原对象中的数据。

方法一:Object.assign

Oject.assign是ES6中Object的一个方法,可以用于多个对象合并等功能,其中就可以对对象进行浅拷贝,该方法的第一个参数是拷贝的目标对象,后面的参数是拷贝的数据来源,可以是多个对象

Object.assign的语法为:Object.assisn(target, ...sources)

使用Object.assign方法有几点需要注意:

  • 不会拷贝对象的继承属性
  • 不会拷贝对象不可枚举的属性
  • 可以拷贝Symbol类型的数据

方法二:扩展运算符

语法:{...obj}

注意事项:

Object.assign

2.深拷贝的原理和实现

不同于浅拷贝只拷贝了引入数据类型的一层地址,深拷贝是在堆中新开辟出新的内存空间,并将原对象完全复制过来存放,这两个对象互相独立的,彻底实现内存上的分离。

方法一:JSON.stringify

JSON.stringify()是目前开发过中最简单的深拷贝方法,把对象序列化为json的字符串,并将对象内容转化为字符串,再用JSON.parse()将字符串生成一个新对象

该方法的局限性:

  • 会忽略Undefined
  • 会忽略Symbol
  • 不能序列化函数
  • 无法拷贝不可枚举属性
  • 无法拷贝对象的原型链
  • 拷贝RegExp会变为空对象
  • 拷贝Date会变成字符串
  • 对象中含有NaN,Infinity以及-Infinity,json序列化结果会变成null
  • 不能解决循环引用的对象,即对象成环
JSON.stringify(JSON.parse(obj))

方法二:递归

利用递归可以实现实现一个深拷贝,同JSON.stringify一样,还是有一些问题没有解决:

  • 不能复制不可枚举的属性
  • 这种方式只能针对普通的引用类型的值做递归,对于Array,Date,RegExp,Error,Function这样的引用类型不能正确的拷贝
  • 不能解决循环引用对象(对象成环)
function deepClone(obj) {
    let cloneObj = {}
    for(let key in obj) {
        if (typeof in obj[key] === 'object') {
            cloneObj = deepClone(obj[key])
        } else {
            cloneObj = obj[key]
        }
    }
    return cloneObj
}

这种基础版本的写法比较简单,可以应对大部分的应用情况。

方法三:改进版

针对前两种方法的待解决问题,可以通过如下方式解决:

  • 针对对象不可枚举的属性以及Symbol类型,使用Reflect.ownKeys方法
  • 当参数为Date,RegExp类型,直接生成一个新的实例返回
  • 利用Object.getOwnPropertyDescriptors方法可以获得对象的所有属性和对应的也行,结合Object.create方法创建一个新对象,并继承传入原对象的原型链
  • 利用WeakMap类型作为hash表,因为WeakMap是弱引用类型,可以有效的防止内存泄露,作为检测循环引用很有帮助,如果存在循环,则直接返回WeakMap中的值
const isDateType = obj => (typeof obj === 'object' || obj === 'function') && (obj !== null)
​
const deepClone = function(obj, hash = new WeakMap()) {
    // 判断时间类型
    if (obj.constructor === Date) {
        return new Date(obj)
    }
    
    // 判断正则
    if (obj.constructor === RegExp) {
        return RegExp(obj)
    }
    
    // 如果循环引用直接使用WeakMap返回
    if (hash.has(obj)) {
        return hash.get(obj)
    }
    
    let allDesc = Object.getOwnPropertyDescriptors(obj)
    // 遍历传入参数所有键的特性
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
    
    // 继承原型链
    hash.set(obj, cloneObj);
    
    for (let key of Reflect.ownKeys(obj)) {
        cloneObj[key] = (isDateType(obj[key]) && obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
    }
    
    return cloneObj
}

补充

WeakMap和Map的却别:

  • WeakMap只接受对象作为键名(null除外),不接受其他类型的参数
  • WeakMap的键名所指的对象不计入垃圾回收机制,WeakMap它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内,因此,只要所引用的对象其他引用都被清除,垃圾回收机制就会释放改对象所占内存,所以,一旦不需要,WeakMap里的键名对象和对应的键值对会自动消失,不用手动删除引用。
  • 没有遍历操作(没有keys(),values(),entries()方法),也没有size属性,因此没有办法列出所有键名,某个键名是否存在完全不可预测,和垃圾回收机制是否运行相关,这一刻,可以取到键名,下一刻垃圾回收机制突然运行,这个键名就消失了,为了防止出现不确定情况,因此统一规定不能取到键名。
  • 无法清空,即布置clear()方法,因此WeakMap只有四个方法可用:get(),set(),has(), delete()