JavaScript对象复制的四种浅拷贝与三种深拷贝方法详解

作者:拾光

时间:2026-10-25

浏览:620

本文详细对比了JavaScript中实现对象复制的多种方案。浅拷贝包括for in遍历、解构赋值和Object.assign,分别讨论了对引用关系、Symbol及原型链的处理差异。深拷贝部分重点解析了JSON序列化转换的局限性,以及通过defineProperty和递归算法实现完整深拷贝的具体逻辑与代码示例。

文章目录

本文分享了一段利用JavaScript实现对象复制功能的具体代码示例,供读者参考学习。

复制

1.遍历复制(for in)

该方法的特点是保持原有的引用关系不变,即原对象的属性依然存在。它仅能复制字符串类型的属性,无法处理Symbol类型和不可枚举属性,也无法复制原型链上的属性,因此属于浅复制。

2.JSON.parse(JSON.stringify(obj)) 转换复制

此方式会修改引用关系,相当于创建了一个全新的对象,不再共享原有属性。它同样只能复制字符串类型的属性,无法处理Symbol、不可枚举属性、原型链以及函数和其他复杂类型,但被视为一种深复制手段。

3.{…obj}解构赋值复制

该方式会修改引用关系,但优势在于能够复制Symbol类型和函数。不过,它无法处理不可枚举属性和原型链,因此仍属于浅复制范畴。

4.Object.assign()对象复制

该方法不修改引用关系,可以复制属性、方法以及Symbol类型。但它无法处理不可枚举属性和原型链,属于浅复制。

深复制**

1.使用defineProperty***

function cloneObject(source,target){
    if(source === null||source === undefined) return source;
    if(source===document) return;
    //判断target是不是继承对象的实例,是不是引用类型(null,undefined,Boolean,string,number都不是引用类型)
    if(!Object.prototype.isPrototypeOf(target)){
        //判断源对象是不是dom元素
        if(HTMLElement.prototype.isPrototypeOf(source)){
            //创建dom元素
             target = document.createElement(source.nodeName);
        }else if(source.constructor === RegExp){
            // 任何正则表达式都有source和flags,source是正则内容,flags是正则修饰符
            // 因为这两个属性都是只读属性,不能写入,必须通过构造函数创建时带入
            target = new RegExp(source.source,source.flags);
        }else if(source.constructor === Date){
            // 日期对象在创建的对象中将原有的日期对象放入,可以让当前日期对象变为原有日期对象的值,但是没有引用关系
            targer = new Date(source);
        }else if(source.constructor === Function){
            // 复制函数,通过正则表达式将函数中的参数以及函数体内容提取到数组中,然后通过new Function()创建
            var arr = source.toString().replace(/\n|\r/g,"").trim().match(/\((.*?)\)\s*\{(.*)\}/).slice(1);
            target = new Function(arr[0].trim(),arr[1]);
        }else if(source.constructor === Set){
            // set类型,在处理时,new Set时可以带入数组,因此我们将原有set的列表强转为数组,并且将这个强转后的数组复制给新数组
            target = new Set(cloneObject(Array.from(source.values())));
        }else if(source.constructor === Map){
            target = new Map();
            // 如果是map类型,遍历map中每个元素
            for(var [key,value] of source.entries()){
                // 如果key是引用类型,
                if(Object.prototype.isPrototypeOf(key)){
                     // 如果value引用类型,则将key和value分别做复制,并且将返回的结果放在map中
                    if(Object.prototype.isPrototypeOf(value)){
                        target.set(cloneObject(key),cloneObject(value));
                    }else{
                         //如果value不是引用类型,只将key复制,并且放入map
                        target.set(cloneObject(key),value);
                    }
                }else{
                    // 这是key不是引用类型时
                    if(Object.prototype.isPrototyeOf(value)){
                        target.set(key,cloneObject(value));
                    }else{
                        target.set(key,value);
                    }
                }
            }
        }
        else{
        //除了null和undefined,其他类型都有constructor。任何对象的constructor都是它的类型,利用其constructor创建对象
        //通过对象类型的反射创建新的同类型对象
            target = new source.constructor();
        }
        
    }
    //获取对象的所有字符属性名和Symbol属性名的数组
    var names = Object.getOwnPropertyName(source).concat(Object.getOwnPropertySymbols(source));
    for(var i = 0;i < names.lenght; i++){
        // 如果当前复制的是函数,并且这个函数的属性是prototype,那么这个属性不复制,否则会死循环
        if(source.constructor === Function&&names[i] === "prototype")
            continue;
        // 获取当前属性名的描述对象
        var desc = Object.getOwnPropertyDescriptor(source,names[i]);
        //   这个描述对象的值如果是引用类型
        if(Object.prototype.isPrototypeOf(desc.value)){
             // 根据需要将源对象的描述内容设置给当前目标对象相同属性名的描述内容,及值付为刚才创建相同类型的对象
            Object.defineProperty(target,names[i],{
                enumerable:desc.enumerable,
                configurable:desc.configurable,
                writable:desc.writable,
                value:cloneObject(desc.value)
            });
        }else{
             //如果描述的对象的值不是引用类型,直接将描述对象设置给目标对象的这个属性
            Object.defineProperty(target,names.desc);
        }
    }
    return target;
}


//原型.isPrototypeOf(对象)。对象里面是不是拥有xx的原型
//反射:通过对象

2.使用JSON对象实现深复制

利用**JSON.parse(str)**可以将字符串转换成对象;

利用**JSON.stringify(obj)**将对象转换成对象形式的字符串。需要注意的是,该方法无法转换对象中的方法,可以考虑先将对象中的方法使用toString()转为字符串,然后再转换使用JSON.stringify(obj);

*使用**JSON.parse(str)**可以将字符串转换成对象

var obj={
    a:1,
    b:3,    //ab复制不会随其中一个的变化而变化
    c:{        //c复制会随其中一个的变化而变化,因为c属于对象的地址引用关系
        d:10,
        e:20
    }
};
var o1 = JSON.parse(JSON.stringify(obj)); //将obj复制给o1

3.使用递归实现深复制

该结构仅限于对象。如果存在数组、正则、日期对象、dom对象则不能使用。

var obj = {
a:1,
b:2,
c:{
        a:1,
  b:2,
  c:{
            a:1,
      b:2,
      }
}
}
//函数定义参数时,注意必要参数写在前面,非必要参数写在后面
function cloneObj(source,target){
    if(target === undefined) target = {};
for(var prop in source){
  //如果
        if(typeof source[prop] === "object" && source[prop] != null){
      target[prop] = {};
      cloneObj(source[prop],target[prop]);
  }else{
            target[prop] = source[prop];
  }
}
return target;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章