实现深拷贝

深浅拷贝

浅拷贝: 仅仅是复制了引用(地址)。即复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响 。
深拷贝: 在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响。
深浅拷贝的主要区别就是:复制的是引用(地址)还是复制的是实例。

不使用第三方库实现

比如现在我们要对下面这个对象做深拷贝,在不使用第三方库的情况下,该如何实现呢。

const source = {
    field1: 1,
    field2: undefined,
    field3: 'hello',
    field4: null,
    field4: {
        child: 'xiaoming',
        child2: {
            child2: 'ahong'
        }
    },
    fieldArray: [1, 2, 3, 4]
}

obj -> JsonString -> newObj

JSON.parse(JSON.stringify(source));

动手实现

  • 原始数据类型,直接复制
  • 引用类型,创建一个新对象,对各属性深拷贝 赋值给新对象

秉着上述两个方向,第一个简单的的深拷贝就实现了。
1.1

function clone2(target){
    if(typeof target == 'object' && target != null){
        let cloneTarget = Array.isArray(target) ? [] : {}
        for(let key in target){
            cloneTarget[key] = clone2(target[key])
        }
        return cloneTarget
    }else{
        return target
    }
}
这种实现如果出现了引用了本身的情况,source.XXX = source 栈溢出。

1.2
如何优化呢,开辟一块新的存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象。

function clone3(target, targetMap = new Map()){
    if(typeof target == 'object' && target != null){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        for(let key in target){
            cloneTarget[key] = clone3(target[key], targetMap)
        }
        return cloneTarget
    }else{
        return target
    }
}

Map为强引用,如果这个引用的层级很高,垃圾回收机制不会主动帮我们回收,可能会出现性能问题。那么可以考虑把Map替换为WeakMap, WeakMap为ES6提供的原生数据结构,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。

function clone4(target, targetMap = new WeakMap()){
    if(typeof target == 'object' && target!=null){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        for(let key in target){
            cloneTarget[key] = clone4(target[key], targetMap)
        }
        return cloneTarget
    }else{
        return target
    }
}

1.3
众所周知,for in 的速度是慢于for循环和while的,那么改造一下循环。

function clone5(target, targetMap = new WeakMap()){
    if(typeof target == 'object'  && target!=null ){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        let keys = Array.isArray(target) ? null : Object.keys(target)
        let length = (keys || target).length
        let index = -1
        while(++index < length){
            let key = index
            if(keys){
                key = (keys || target)[index]
            }
            cloneTarget[key] = clone5(target[key], targetMap)
        }
        return cloneTarget
    }else{
        return target
    }
}

将while循环抽离出来

function customForEach(array, iteratee) {
    let index = -1
    while(++index < array.length){
        iteratee(array[index], index)
    }
    return array
}

function clone6(target, targetMap = new WeakMap()){
    if(typeof target == 'object' && target!=null ){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        let keys = Array.isArray(target) ? null : Object.keys(target) 
        customForEach(keys || target, (value, key) => {
            if(keys){
                key = value
            }
            cloneTarget[key] = clone6(target[key], targetMap)
        })
        return cloneTarget
    }else{
        return target
    }
}

这只是简易版的,适合大部分业务数据的深拷贝,还未考虑到类型为function等情况。详细的可见原文,或者看下lodash的源码

你可能感兴趣的:(javascript)