手写JavaScript深浅拷贝(递归法、迭代法、考虑循环引用)

7 阅读4分钟

手写JavaScript深浅拷贝(递归法、迭代法、考虑循环引用)

JavaScript可粗略的分为基本类型(Number,String,Boolean,Null,Undefined,Symbol)和引用类型(Object,Array,Function,RegExp,Date,Map,Set...),其中引用类型如果想要去复制一个内容完全相同的对象,有以下几种内置方法

克隆内置方法局限
JSON.parse(JSON.stringify)①会忽略不可枚举(enumerable:false)的属性②不支持Symbol,Function,强制把Date转为字符串③遇到循环引用会报错
Object.assign({},originalObj)浅拷贝专用
lodash.cloneDeep()需引用第三方库,安全性无法保证

因此有手写深拷贝的需要,同时深拷贝也可以通过递归(类似树的前序遍历)和迭代实现(类似树的层序遍历)实现,其中递归法可能由于超出栈深而导致爆栈

手写浅克隆:

//浅克隆
function shallowClone(obj){
    let res = {};
    //复制字符串属性
    for(let strPro of Object.getOwnPropertyNames(obj)){
        res[strPro]=obj[strPro];
    }
    //复制Symbol属性
    for(let symbolPro of Object.getOwnPropertySymbols(obj)){
        res[symbolPro]=obj[symbolPro];
    }
    return res;
}
console.log(shallowClone(obj));
console.log(shallowClone([1,2,3,4,5]));//数组本质上是下标字符串为属性的对象

手写深克隆(递归法):

//深克隆(递归)
function myDeepClone(obj){
    let res = {};
    //复制字符串属性
    for(let strPro of Object.getOwnPropertyNames(obj)){
        //如果属性值是引用类型,那么进行递归(函数类型不递归)
        if(obj[strPro] instanceof Object){
            if(typeof obj[strPro] === 'object'){
                res[strPro] = myDeepClone(obj[strPro]);
            }
            if(typeof obj[strPro] === 'function'){
                res[strPro] = obj[strPro];
            }
        }else{
            res[strPro] = obj[strPro];
        }
    }
    //复制Symbol属性
    for(let symbolPro of Object.getOwnPropertySymbols(obj)){
        if(obj[symbolPro] instanceof Object){
            if(typeof obj[symbolPro] === 'object'){
                res[symbolPro] = myDeepClone(obj[symbolPro]);
            }
            if(typeof obj[symbolPro] === 'function'){
                res[symbolPro] = obj[symbolPro];
            }
        }else{
            res[symbolPro] = obj[symbolPro];
        }
    }
    return res;
}

let obj1 = {};
let anoSymbol = Symbol(4);
obj1[anoSymbol] = function anoPrint(){
    console.log('print ano');
}
obj1.strPro1 = 'haha';
obj1.strPro2 = {
    pro2Pro1 :'999',
    pro2Pro2 :()=>{console.log('pro2Pro2');},
    pro2Pro3 :{
        pro3Pro1:4,
        pro3Pro2:Symbol(8)
    }
}
console.log(obj1,'sourse');
console.log(myDeepClone(obj1));

深克隆(递归,考虑原型链):

//深克隆(递归,考虑原型链)
function myDeepCloneConsiderProto(obj){
    let res;
    //这部分是obj为数组的类型
    if(obj instanceof Array){
        res=[];//实现数组原型链的保留
        for(let i=0; i<obj.length; i++){
            let item = obj[i];
            if(Array.isArray(item)){
                res[i]=myDeepCloneConsiderProto(item);
            }else{
                res[i]=item;
            }
        }
        return res;
    }
    //以下代码都是obj为对象的类型
    //复制字符串属性
    else if(obj instanceof Object){
        res={};
        for(let strPro of Object.getOwnPropertyNames(obj)){
            //如果属性值是引用类型,那么进行递归(函数类型不递归)
            if(obj[strPro] instanceof Object){
                if(typeof obj[strPro] === 'object'){
                    res[strPro] = myDeepClone(obj[strPro]);
                }
                if(typeof obj[strPro] === 'function'){
                    res[strPro] = obj[strPro];
                }
            }else{
                res[strPro] = obj[strPro];
            }
        }
        //复制Symbol属性
        for(let symbolPro of Object.getOwnPropertySymbols(obj)){
            if(obj[symbolPro] instanceof Object){
                if(typeof obj[symbolPro] === 'object'){
                    res[symbolPro] = myDeepClone(obj[symbolPro]);
                }
                if(typeof obj[symbolPro] === 'function'){
                    res[symbolPro] = obj[symbolPro];
                }
            }else{
                res[symbolPro] = obj[symbolPro];
            }
        }
        return res;
    }
}

深拷贝(考虑去除循环应用bug,递归):

//深拷贝(考虑去除循环应用bug,递归)
function myDeepCloneConsiderCircle(obj,map=new Map()){
    let res = {};
    //把遍历的每一个obj的副本res都存进共享的哈希表里
    map.set(obj,res);
    //复制字符串属性
    for(let strPro of Object.getOwnPropertyNames(obj)){
        //如果属性值是引用类型,那么进行递归(函数类型不递归)
        if(obj[strPro] instanceof Object){
            if(typeof obj[strPro] === 'object'){
                if(!map.has(obj[strPro])){
                    res[strPro] = myDeepClone(obj[strPro],map);
                    map.set(obj[strPro],obj[strPro]);
                }else{
                    res[strPro] = map.get(obj[strPro]);
                }
            }
            if(typeof obj[strPro] === 'function'){
                res[strPro] = obj[strPro];
            }
        }else{
            res[strPro] = obj[strPro];
        }
    }
    //复制Symbol属性
    for(let symbolPro of Object.getOwnPropertySymbols(obj)){
        if(obj[symbolPro] instanceof Object){
            if(typeof obj[symbolPro] === 'object'){
                if(!map.has(obj[symbolPro])){
                    res[symbolPro] = myDeepClone(obj[symbolPro],map);
                    map.set(obj[symbolPro],obj[symbolPro]);
                }else{
                    res[symbolPro] = map.get(obj[symbolPro]);
                }
            }
            if(typeof obj[symbolPro] === 'function'){
                res[symbolPro] = obj[symbolPro];
            }
        }else{
            res[symbolPro] = obj[symbolPro];
        }
    }
    return res;
}

深克隆(递归法,且考虑所有数据类型和循环引用情况):

//深克隆(递归法,且考虑所有数据类型和循环引用情况)
const myCompleteDeepClone = (target, map = new Map()) => {
    if (typeof target === 'object' && target !== null) {
        if (/^(Function|RegExp|Date|Map|Set)$/.test(target.constructor.name)) {
            return new target.constructor(target);
        }
        if (map.has(target)) {
            return map.get(target);
        }
        let res = new target.constructor();
        map.set(target, res);
        let strProArr = Object.getOwnPropertyNames(target);
        let symbolProArr = Object.getOwnPropertySymbols(target);
        for (let item of [...strProArr, ...symbolProArr]) {
            res[item] = myCompleteDeepClone(target[item], map);
        }
        return res;
    } else {
        return target;
    }
}

深克隆(迭代法):

//深克隆(迭代法)
function myDeepCloneIterate(obj){
    let res=new obj.constructor();
    let que=[];
    let map=new Map();
    map.set(obj,res);
    que.push(obj);

    while(que.length){
        let len=que.length;
        for(let i=0;i<len;i++){
            let tar= que.shift();
            let copy = map.get(tar);
            let strProArr = Object.getOwnPropertyNames(tar);
            let symbolProArr = Object.getOwnPropertySymbols(tar);
            for(let item of [...strProArr,...symbolProArr]){
                if(typeof tar[item] !== 'object'){
                    copy[item] = tar[item];
                }else{
                    copy[item] = new tar[item].constructor();
                    map.set(tar[item],copy[item]);
                    que.push(tar[item]);
                }
            }
        }
    }
    return res;
}

基本思路: ①对递归:整体模式都是对于某个待克隆的对象,在每次递归执行的时候先创建一个res,填充res并返回。如果需要考虑循环引用问题,就在每次递归函数开始执行的时候,先检查map中是否有已记录的对象,如果有就返回,没有就用set记录,键值对格式为[target,res] ②对迭代:思路来自树的层序遍历,用队列存储节点,以每层为单位展开循环。用map来保存每个target属性克隆出的对象res,这样就能实现遍历target的时候,res也能自动向深层扩散。