深拷贝 订阅
一个引用对象一般来说由两个部分组成:一个具名的Handle,也就是我们所说的声明(如变量)和一个内部(不具名)的对象,也就是具名Handle的内部对象。它在Manged Heap(托管堆)中分配,一般由新增引用对象的New方法是进行创建。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。 展开全文
一个引用对象一般来说由两个部分组成:一个具名的Handle,也就是我们所说的声明(如变量)和一个内部(不具名)的对象,也就是具名Handle的内部对象。它在Manged Heap(托管堆)中分配,一般由新增引用对象的New方法是进行创建。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。
信息
要    求
复制对象空间和资源
特    点
源对象与拷贝对象互相独立
有关术语
拷贝构造函数
中文名
深拷贝
学    科
软件工程
外文名
deep copy
深拷贝简介
如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,浅拷贝会带来数据安全方面的隐患。在进行赋值之前,为指针类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝 [1]  。这种拷贝称为深拷贝。深拷贝有两种方式实现:层层clone的方法和利用串行化来做深拷贝。层层clone的方法:在浅拷贝的基础上实现,给引用类型的属性添加克隆方法,并且在拷贝的时候也实现引用类型的拷贝。此种方法由于要在多个地方实现拷贝方法,可能会造成混论。利用串行化来做深拷贝:为避免复杂对象中使用clone方法可能带来的换乱,可以使用串化来实现深拷贝。先将对象写到流里,然后再从流里读出来。
收起全文
精华内容
参与话题
问答
  • 深拷贝与浅拷贝的区别,实现深拷贝的几种方法

    万次阅读 多人点赞 2018-08-28 11:09:49
    如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。 此篇文章中也会简单阐述到栈堆,基本...

    转自https://www.cnblogs.com/echolun/p/7889848.html 

    如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

    此篇文章中也会简单阐述到栈堆基本数据类型引用数据类型,因为这些概念能更好的让你理解深拷贝与浅拷贝。

    我们来举个浅拷贝例子:

    let a=[0,1,2,3,4],
        b=a;
    console.log(a===b);
    a[0]=1;
    console.log(a,b);

     

    嗯?明明b复制了a,为啥修改数组a,数组b也跟着变了,这里我不禁陷入了沉思。

    那么这里,就得引入基本数据类型与引用数据类型的概念了。

    面试常问,基本数据类型有哪些,number,string,boolean,null,undefined五类。

    引用数据类型(Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等。

    而这两类数据存储分别是这样的:

    a.基本类型--名值存储在栈内存中,例如let a=1;

    当你b=a复制时,栈内存会新开辟一个内存,例如这样:

     

    所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

    b.引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图:

    当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。

    而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。

     

    那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,起步就达到深拷贝的效果了

    1.我们怎么去实现深拷贝呢,这里可以递归递归去复制所有层级属性。

    这么我们封装一个深拷贝的函数

    复制代码

    function deepClone(obj){
        let objClone = Array.isArray(obj)?[]:{};
        if(obj && typeof obj==="object"){
            for(key in obj){
                if(obj.hasOwnProperty(key)){
                    //判断ojb子元素是否为对象,如果是,递归复制
                    if(obj[key]&&typeof obj[key] ==="object"){
                        objClone[key] = deepClone(obj[key]);
                    }else{
                        //如果不是,简单复制
                        objClone[key] = obj[key];
                    }
                }
            }
        }
        return objClone;
    }    
    let a=[1,2,3,4],
        b=deepClone(a);
    a[0]=2;
    console.log(a,b);

    复制代码

    可以看到

    跟之前想象的一样,现在b脱离了a的控制,不再受a影响了。
    这里再次强调,深拷贝,是拷贝对象各个层级的属性,可以看个例子。JQ里有一个extend方法也可以拷贝对象,我们来看看
    let a=[1,2,3,4],
        b=a.slice();
    a[0]=2;
    console.log(a,b);

    那是不是说slice方法也是深拷贝了,毕竟b也没受a的影响,上面说了,深拷贝是会拷贝所有曾经的属性,还是这个例子,我们把a改改

    let a=[0,1,[2,3],4],
            b=a.slice();
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);

    拷贝的不彻底啊,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的深拷贝。

    这里引用知乎问答里面的一张图

     

    第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

    同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。

    2.除了递归,我们还可以借用JSON对象的parse和stringify

    复制代码

    function deepClone(obj){
        let _obj = JSON.stringify(obj),
            objClone = JSON.parse(_obj);
        return objClone
    }    
    let a=[0,1,[2,3],4],
        b=deepClone(a);
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);

    复制代码

    可以看到,这下b是完全不受a的影响了。

    附带说下,JSON.stringify与JSON.parse除了实现深拷贝,还能结合localStorage实现对象数组存储。有兴趣可以阅读博客这篇文章。

    localStorage存储数组,对象,localStorage,sessionStorage存储数组对象

    3.除了上面两种方法之外,我们还可以借用JQ的extend方法。

    $.extend( [deep ], target, object1 [, objectN ] )

    deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝

    target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

    object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。 

    let a=[0,1,[2,3],4],
        b=$.extend(true,[],a);
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);

    可以看到,效果与上面方法一样,只是需要依赖JQ库。

    说了这么多,了解深拷贝也不仅仅是为了应付面试题,在实际开发中也是非常有用的。例如后台返回了一堆数据,你需要对这堆数据做操作,但多人开发情况下,你是没办法明确这堆数据是否有其它功能也需要使用,直接修改可能会造成隐性问题,深拷贝能帮你更安全安心的去操作数据,根据实际情况来使用深拷贝,大概就是这个意思。

    本文算是个人对于深浅拷贝的学习笔记整理,这里借用了以下资料的思想。

    【 js 基础 】 深浅拷贝

    js面试题:实现对象深度克隆(deepClone)的三种方案

    javascript中的深拷贝和浅拷贝?

    展开全文
  • 深拷贝的三种方式

    2020-03-05 12:35:01
    深拷贝:拷贝值 浅拷贝: 拷贝地址 深拷贝的方法 //待拷贝的对象 let a = { name: '张三', grade: { chinese: 23, math: 90, }, sex: '男', friend: [{id: '李四'},'王五'], date: new Date().toString(), .....

    深拷贝:拷贝值

    浅拷贝: 拷贝地址

    深拷贝的方法
    //待拷贝的对象
    let a = {
        name: '张三',
        grade: {
            chinese: 23,
            math: 90,
        },
        sex: '男',
        friend: [{id: '李四'},'王五'],
        date: new Date().toString(),
        call() {
            console.log('call()')
        }
    }
    
    1. JSON 对象
    JSON.parse(JSON.stringify(a)): a 只能是扁平对象
    //不能拷贝 function,会直接丢弃,ye
    
    
    console.log('JSON 深拷贝a', JSON.parse(JSON.stringify(a)))
    //打印结果
    /*
      { name: '张三',
      grade: { chinese: 23, math: 90 },
      sex: '男',
      friend: [ { id: '李四' }, '王五' ],
      date: 'Thu Mar 05 2020 11:45:05 GMT+0800 (GMT+08:00)' 
      }
    */
    
    1. Object.assign(target,source)
    //如果只有一级属性,则就是深拷贝
    //如果一级属性里面有引用数据类型,则这个只是浅拷贝了
    console.log(Object.assign({},a))
    /*
    打印结果
    { name: '张三',    
      grade: { chinese: 23, math: 90 },
      sex: '男',
      friend: [ { id: '李四' }, '王五' ],
      date: 'Thu Mar 05 2020 11:47:39 GMT+0800 (GMT+08:00)',
      call: [Function: call] }
    */
    
    1. for in 递归拷贝
    /* for in 递归拷贝会将 原型上的属性方法全部拷贝过来 */
    function clone(source) {
        let target = source instanceof Array ? [] : {}
        for (let key in source) {
            if (typeof source[key] === 'object') {
                target[key] = clone(source[key])
            } else {
                target[key] = source[key]
            }
        }
        return target
    }
    /*
    打印结果
    { name: '张三',
      grade: { chinese: 23, math: 90 },
      sex: '男',
      friend: [ { id: '李四' }, '王五' ],
      date: 'Thu Mar 05 2020 12:03:24 GMT+0800 (GMT+08:00)',
      call: [Function: call] }//函数还是同一个函数,函数没有做到深拷贝
    */
    
    
    展开全文
  • 一篇文章彻底搞懂浅拷贝和深拷贝

    万次阅读 多人点赞 2018-07-16 16:36:32
    强烈推荐30个原生JavaScript的demo,包括canvas时钟特效、自定义视频...【javascript】详解javaScript的深拷贝 目录 浅谈深拷贝和浅拷贝 深拷贝和浅拷贝的区别 为什么要使用深拷贝深拷贝的要求程度...

    强烈推荐30个原生JavaScript的demo,包括canvas时钟特效、自定义视频播放器、搜索栏快速匹配、fetch访问资源、console调试技巧等,先fork后学习,详见点击打开链接,欢迎点赞~~~谢谢,共同进步学习!

     

    由博主《前端初级工程师面试系列一JS基础》文章一JS变量类型引伸的考点,变量类型分为基本类型和引用类型,那么在变量拷贝赋值时,也是不一样的,分为浅拷贝和深拷贝,是面试中常考的知识点,也是实际开发中经常会用到的内容。

    目录

    正文

    前言: 最开始意识到深拷贝的重要性是在我使用redux的时候(react + redux), redux的机制要求在reducer中必须返回一个新的对象,而不能对原来的对象做改动,事实上,当时我当然不会主动犯这个错误,但很多时候,一不小心可能就会修改了原来的对象,例如:var newObj = obj; newObj.xxx = xxx  实际上,这个时候newObj和obj两个引用指向的是同一个对象,我修改了newObj,实际上也就等同于修改了obj,这,就是我和深浅拷贝的第一次相遇。

    深拷贝和浅拷贝的区别

    1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用

    2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

    为什么要使用深拷贝?

    我们希望在改变新的数组(对象)的时候,不改变原数组(对象)

    深拷贝的要求程度

    我们在使用深拷贝的时候,一定要弄清楚我们对深拷贝的要求程度:是仅“深”拷贝第一层级的对象属性或数组元素还是递归拷贝所有层级的对象属性和数组元素?

    怎么检验深拷贝成功

    改变任意一个新对象/数组中的属性/元素,     都不改变原对象/数组

    只对第一层级做拷贝

    深拷贝数组(只拷贝第一级数组元素) 

    1. 直接遍历
    var array = [1, 2, 3, 4];
    function copy (array) {
       let newArray = []
       for(let item of array) {
          newArray.push(item);
       }
       return  newArray;
    }
    var copyArray = copy(array);
    copyArray[0] = 100;
    console.log(array); // [1, 2, 3, 4]
    console.log(copyArray); // [100, 2, 3, 4]

    该方法不做解释(逃...)

    2. slice()

    var array = [1, 2, 3, 4];
    var copyArray = array.slice();
    copyArray[0] = 100;
    console.log(array); // [1, 2, 3, 4]
    console.log(copyArray); // [100, 2, 3, 4]

    slice() 方法返回一个从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!)

    用法:array.slice(start,end) start表示是起始元素的下标, end表示的是终止元素的下标

    当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组

    3. concat()

    var array = [1, 2, 3, 4];
    var copyArray = array.concat();
    copyArray[0] = 100;
    console.log(array); // [1, 2, 3, 4]
    console.log(copyArray); // [100, 2, 3, 4]

    concat() 方法用于连接两个或多个数组。( 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。)

    用法:array.concat(array1,array2,......,arrayN)

    因为我们上面调用concat的时候没有带上参数,所以var copyArray = array.concat();实际上相当于var copyArray = array.concat([]);也即把返回数组和一个空数组合并后返回

     

    但是,事情当然不会这么简单,我上面的标题是 “深拷贝数组(只拷贝第一级数组元素)”,这里说的意思是对于一级数组元素是基本类型变量(如number,String,boolean)的简单数组, 上面这三种拷贝方式都能成功,但对第一级数组元素是对象或者数组等引用类型变量的数组,上面的三种方式都将失效,例如:

    
    var array = [
      { number: 1 },
      { number: 2 },
      { number: 3 }
    ];
    var copyArray = array.slice();
    copyArray[0].number = 100;
    console.log(array); //  [{number: 100}, { number: 2 }, { number: 3 }]
    console.log(copyArray); // [{number: 100}, { number: 2 }, { number: 3 }]

    深拷贝对象

    1.直接遍历

    var obj = {
      name: '彭湖湾',
      job: '学生'
    }
    
    function copy (obj) {
       let newObj = {};
         for (let item in obj ){
           newObj[item] = obj
         }
         return newObj;
    }
     var copyObj = copy(obj);
    copyObj.name = '我才不是彭湖湾呢! 哼 (。・`ω´・)';
    console.log(obj); // {name: "彭湖湾", job: "学生"}
    console.log(copyObj); // {name: "我才不是彭湖湾呢! 哼 (。・`ω´・)", job: Object}

    该方法不做解释(逃...)

    2.ES6的Object.assign

    var obj = {
      name: '彭湖湾',
      job: '学生'
    }
    var copyObj = Object.assign({}, obj);
    copyObj.name = '我才不叫彭湖湾呢! 哼  (。・`ω´・)';
    console.log(obj);   // {name: "彭湖湾", job: "学生"}
    console.log(copyObj);  // {name: "我才不叫彭湖湾呢! 哼  (。・`ω´・)", job: "学生"}

    Object.assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target

    用法: Object.assign(target, source1, source2);  所以 copyObj = Object.assign({}, obj);  这段代码将会把obj中的一级属性都拷贝到 {}中,然后将其返回赋给copyObj

    3.ES6扩展运算符:

    var obj = {
        name: '彭湖湾',
        job: '学生'
    }
    var copyObj = { ...obj }
    copyObj.name = '我才不叫彭湖湾呢! 哼  (。・`ω´・)'
    console.log(obj.name) //   彭湖湾
    console.log(copyObj.name)  // 我才不叫彭湖湾呢! 哼  (。・`ω´・)

    扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中

    ⚠️注意:实际上,无论是使用扩展运算符(...)还是解构赋值,对于引用类型都是浅拷贝。所以在使用splice()、concat()、...对数组拷贝时,只有当数组内部属性值不是引用类型是,才能实现深拷贝。对多层嵌套对象,也即是存在,很遗憾,上面三种方法,都会失败:

    var obj = {
       name: {
          firstName: '彭',
          lastName: '湖湾'
       },
       job: '学生'
    }
    var copyObj = Object.assign({}, obj)
    copyObj.name.lastName = '湖水的小浅湾';
    console.log(obj.name.lastName); // 湖水的小浅湾
    console.log(copyObj.name.lastName); // 湖水的小浅湾

    拷贝所有层级

    有没有更强大一些的解决方案呢?使得我们能够

    1.不仅拷贝第一层级,还能够拷贝数组或对象所有层级的各项值

    2. 不是单独针对数组或对象,而是能够通用于数组,对象和其他复杂的JSON形式的对象

    请看下面:

    下面这一招可谓是“一招鲜,吃遍天”

    1.JSON.parse(JSON.stringify(XXXX))

    var array = [
        { number: 1 },
        { number: 2 },
        { number: 3 }
    ];
    var copyArray = JSON.parse(JSON.stringify(array))
    copyArray[0].number = 100;
    console.log(array); //  [{number: 1}, { number: 2 }, { number: 3 }]
    console.log(copyArray); // [{number: 100}, { number: 2 }, { number: 3 }]

    JSON.parse() 方法用于将一个 JSON 字符串转换为对象--(反序列化)

    JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串--(序列化)

    序列化的缺点:

    1. 不支持基本数据类型的undefined,序列化后将其省略
    2. 不支持函数
    3. Nan,Infinity序列化的结果是null

    能用大招杀的就不要用q杀嘛!!

    2.手动写递归

    你说啥? 你说上面的那种方法太无脑,  一定要自己写一段递归才有做技术的感觉? OK成全你!

    let array = [
       { number: 1 },
       { number: 2 },
       { number: 3 }
    ];
    function copy (obj) {
            //首先判断需要拷贝的“东西”是什么类型
            if(typeof obj !== 'object' || obj == null){
                return;
            }
            let newobj = obj.constructor === Array ? [] : {};
            //obj是数组类型,下面的i就是index;obj是对象,i就是key
            for(let i in obj){
               newobj[i] = typeof obj[i] === 'object' ? copy(obj[i]) : obj[i];
            }
            return newobj
    }
    
    let copyArray = copy(array)
    copyArray[0].number = 100;
    console.log(array); //  [{number: 1}, { number: 2 }, { number: 3 }]
    console.log(copyArray); // [{number: 100}, { number: 2 }, { number: 3 }]

    【注意】上文的所有的示例都忽略了一些特殊的情况: 对对象/数组中的Function,正则表达式等特殊类型的拷贝

    上述代码中还隐藏这两个知识点:

    (1)obj == null 为何不是 obj === null?

    obj==null =>obj为null 或者 obj 为undefined,因为null == undefined,这样写能默认处理两种情况,obj===null ,成立前提只有obj是null

    (2)怎样判断一个对象是不是数组?

    先上方法(确定的方法有两种)

    1. 根据对象的class属性来判断,跨原型链调用toString()方法。  Object. prototype.toString.call(obj)===[ object Array]
    2. Array.isArray直接判断   Array. isArray(obj)。

    补充:

    • 推荐文章:为什么用Object.prototype.toString.call(obj)检测对象类型

      toString为Object的原型方法,返回一个用来描述该对象的字符串,所以可以调用对象原型方法toString()来探明对象的信息。
      那么原型方法怎么调用呢,利用call,将其this指向需要判断的对象, 就可以用toString()方法了。
      let arr=[1,2,3];
      console.log(Array.prototype.hasOwnProperty("toString"));//true
      console.log(arr.toString());//1,2,3 arr作为对象Object的实例,重写了toString()方法。
      delete Array.prototype.toString;//delete操作符可以删除实例属性
      console.log(Array.prototype.hasOwnProperty("toString"));//false
      console.log(arr.toString());//"[object Array]" 
      // 由于删除了实例对象中的toString()方法,找不到,顺着原型链往上走,就调用了对象Object的方法,返回的结果就和Array.prototype.toString(arr)一样的。
    • 根据构造函数来判断 instanceof 操作符可以来表示实例是否属于某个构造函数创建的。

    这种方法有一个问题,就是验证不够严格。 即使对象创建时不是使用数组创建的,但是只要原型链上有数组类型,也认为是数组,亦或者,即便创建时是数组创建,但其原型上有对象类型,便不再被认为是数组。

    推荐文章

    (3)for...in 和for...of,forEach的区别,for...in用于对象复制时需要注意什么?

    for... in特点

    • 遍历对象返回的对象的key值,遍历数组返回的数组的下标(key)。

    • for ... in 会遍历原型上的属性值

    • 遍历返回数据是乱序

    总结一句: for in 循环特别适合遍历对象。

    for... of特点

    • for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name

    • for ... in 会遍历原型上的属性值

    • 遍历返回数据是乱序

    • for of 不同与 forEach, 它可以与 break、continue和return 配合使用,也就是说 for of 循环可以随时退出循环。

    总结一句: for of 比较适合遍历数组,及其他具有遍历器的集合

    forEach特点

    • 使用foreach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。forEach与break和return 不搭

    • forEach()无法在所有元素都传递给调用的函数之前终止遍历

    for…in循环可应用于对象的复制,不过其有一个缺点,就是会从原型属性里继承prototype()属性。 

    例如: 

    let array = [1,2,3,4,5] 
    Array.prototype.age = 13;
    var result = []; 
    for(let i in array){ 
        result.push(array[i]); 
    } 
    alert(result.join(“,”)); 
    result返回结果【1,2,3,4,5,13】 

    如何避免从原型属性里继承prototype()属性,这里使用hasOwnProperty(name),该函数指示一个对象自身(不包括原型链)是否具有指定名称的属性。如果有返回true,如果没有返回false。 

    let array = [1,2,3,4,5] 
    Array.prototype.age = 13; 
    var result = []; 
    for(let i in array){ 
    if(array.hasOwnProperty(i)){ 
        result.push(array[i]); 
    } 
    alert(result.join(“,”)); 
    } 
    result返回结果【1,2,3,4,5】

    所以上面的深拷贝代码应优化为如下: 

    let array = [
       { number: 1 },
       { number: 2 },
       { number: 3 }
    ];
    function copy (obj) {
            //首先判断需要拷贝的“东西”是什么类型
            if(typeof obj !== 'object' || obj == null){
                return;
            }
            let newobj = Array.isArray(obj) ? [] : {};
            //obj是数组类型,下面的i就是index;obj是对象,i就是key
            for(let i in obj){
               if(obj.hasOwnProperty(i)){
                   newobj[i] = typeof obj[i] === 'object' ? copy(obj[i]) : obj[i];
                }
            }
            return newobj
    }
    
    let copyArray = copy(array)
    copyArray[0].number = 100;
    console.log(array); //  [{number: 1}, { number: 2 }, { number: 3 }]
    console.log(copyArray); // [{number: 100}, { number: 2 }, { number: 3 }]

    存在大量深拷贝需求的代码——immutable提供的解决方案

    实际上,即使我们知道了如何在各种情况下进行深拷贝,我们也仍然面临一些问题: 深拷贝实际上是很消耗性能的。(我们可能只是希望改变新数组里的其中一个元素的时候不影响原数组,但却被迫要把整个原数组都拷贝一遍,这不是一种浪费吗?)所以,当你的项目里有大量深拷贝需求的时候,性能就可能形成了一个制约的瓶颈了。

    immutable的作用

    通过immutable引入的一套API,实现:

    1.在改变新的数组(对象)的时候,不改变原数组(对象)

    2.在大量深拷贝操作中显著地减少性能消耗

    先睹为快:

    const { Map } = require('immutable')
    const map1 = Map({ a: 1, b: 2, c: 3 })
    const map2 = map1.set('b', 50)
    map1.get('b') // 2
    map2.get('b') // 50
    展开全文
  • 昨天学习ES6的Object.assign()的时候,发现Object.assign进行的拷贝是浅拷贝,及只有第一层是深拷贝。若拷贝的属性的值是对象的复合属性,就只是拷贝过来一个引用。 const obj1 = {'a': {'b': 1}} const obj2 = ...

    本文部分内容来自https://www.cnblogs.com/echolun/p/7889848.html

    昨天学习ES6的Object.assign()的时候,发现Object.assign进行的拷贝是浅拷贝,及只有第一层是深拷贝。若拷贝的属性的值是对象的复合属性,就只是拷贝过来一个引用。

    const obj1 = {'a': {'b': 1}}
    const obj2 = Object.assign({}, obj1)
     
    obj1.a.b = 2
    obj2.a.b  // 2

    那js的深拷贝和浅拷贝的区别是什么呢

    首先,先了解基本数据类型和引用数据类型。

    基本数据类型:string、number、boolean、undefined、null

    引用数据类型:object、array、function

    (1)基本类型---名值存储在栈内存中,例如 let a = 1

    当b = a时:

    栈内存开辟了一个新内存,所以修改a的值时,b的值并不会随之变化。但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

    (2)引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值

    当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值

    而当我们 a[0]=1 时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。

    要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,起步就达到深拷贝的效果了

    所以我们应该怎么实现深度拷贝呢

    (1)递归去复制所有层级属性

    function deepClone(obj) {
      let objClone = Array.isArray(obj) ? [] : {}
      if (obj && typeof obj === 'object') {
        for (let key in obj) {
          if (obj.hasOwnProperty(key)) {
            // 判断obj子元素是否为对象,如果是,递归复制
            if (obj[key] && typeof obj[key] === 'object') {
              objClone[key] = deepClone(obj[key])
            } else {
              objClone[key] = obj[key]
            }
          }
        }
      }
      return objClone
    }
    
    let a = [1,2,3,4]
    let b = deepClone(a)  // [1,2,3,4]
    a[0] = 2  // [2,2,3,4]
    b  //  [1,2,3,4]

    那是不是说slice方法也是深拷贝了,毕竟b也没受a的影响,上面说了,深拷贝是会拷贝所有曾经的属性,还是这个例子,我们把a改改

    let a = [0,1,[2,3],4]
    let b = a.slice()
    a[0] = 1
    a[2][0] = 1  // [1,1,[1,3],4]
    b  // [0,1,[1,3],4]
    

    拷贝的不彻底啊,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的深拷贝。

    这里引用知乎问答里面的一张图

    第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

    同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。

    (2)用JSON对象的parse和stringify

    function deepClone (obj) {
      let _obj = JSON.stringify(obj)
      let objClone = JSON.parse(_obj)
      return objClone
    }
    
    let a = [0,1,[2,3],4]
    let b = deepClone(a)
    a[0] = 1  
    a[2][0] = 1  // [1,1,[1,3],4]
    b  // [0,1,[2,3],4]

    (3)JQ的extend方法

    JQ里有extend方法也可以拷贝对象。

    $.extend([deep ], target, object1 [, objectN ])

    deep表示是否深拷贝,为true为深拷贝;为false,为浅拷贝。

    target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

    object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。 

    let a = [0,1,[2,3],4]
    let b = $.extend(true, [], a)
    a[0] = 1
    a[2][0] = 1  // [1,1,[1,3],4]
    b  // [0,1,[2,3],4]

     

    展开全文
  • 深拷贝

    2019-09-06 18:08:52
    最简单的深拷贝(JSON.stringify() 和JSON.parse()) 先把对象使用JSON.stringify()转为字符串,再赋值给另外一个变量,然后使用JSON.parse()转回来即可
  • C++细节 深拷贝和浅拷贝(位拷贝)详解

    万次阅读 多人点赞 2018-08-07 21:00:14
    在对象拷贝过程中,如果没有自定义拷贝构造函数,系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型成员变量,调用其相应类型的拷贝构造函数。 阅读《高质量的...
  • 深拷贝的四种方式

    2020-02-25 15:35:41
    2、深拷贝  概念:拷贝对象各个层级的属性。简单的讲,就是复制出来的每个对象都有属于自己的内存空间,不会互相干扰。 实现方式: 实现Cloneable接口,并且重写Object类中的clone()方法 实现Serializable接...
  • 实现一个深拷贝

    2020-08-27 10:48:13
    总结:深拷贝和浅拷贝 考察的实际上是对内存的理解 基本类型数据存在栈内存中,而对象,数组实际的存储位置是在堆内存中,但是它们的指针存在栈内存中,所以直接拷贝基本类型数据相当于在栈内存中新建一块内存进行...
  • 如何进行深拷贝

    2019-11-20 14:55:29
    1理解深拷贝和浅拷贝 浅拷贝我们用同一个 ,(东西改变后,大家都有影响) 深拷贝我和你用的不是同一个,(我改变后只对自己有影响,对你没影响) 2,理解值类型与引用类型 值类型:number,string,boolean,...
  • 深拷贝:基本类型,引用类型都拷贝一份。 在查找几篇博客之后,之以为懂了深拷贝,浅拷贝,然后还是发现自己对于他们的理解还是出现了问题, 理解1:浅拷贝,深拷贝你调用了几次,就拷贝几次。 比如你调用一次...
  • 深拷贝的三种实现方式

    万次阅读 2018-05-05 15:37:21
    JSON方法实现 //_tmp和result是相互独立的,没有任何联系,有各自的存储空间。 let deepClone = function (obj) { let _tmp = JSON.stringify(obj);//将对象转换为json字符串形式 let result = JSON.parse(_tmp)...
  • 你真的理解深拷贝与浅拷贝了吗?

    千次阅读 2019-06-12 14:31:38
    简单来说,深拷贝主要是将另一个对象的属性值拷贝过来之后,另一个对象的属性值并不受到影响,因为此时它自己在堆中开辟了自己的内存区域,不受外界干扰。 浅拷贝主要拷贝的是对象的引用值,当改变对象的值,另一个...
  • JS深拷贝的几种方法

    千次阅读 2019-03-18 20:32:39
    1. 遍历,就是把对象的属性遍历一遍,赋给一个新的对象。 var deepCopy= function(source) { var result={}; ... for (var key in source) { ... result[key] = typeof source[key]===’object’?...
  • Java深入理解深拷贝和浅拷贝区别

    万次阅读 多人点赞 2019-02-13 23:31:47
    一、拷贝的引入 (1)、引用拷贝 创建一个指向对象的引用变量的拷贝。 Teacher teacher = new Teacher("Taylor",26); Teacher otherteacher = teacher; System.out.println(teacher); System.out.println...
  • 深拷贝和浅拷贝区别是什么?

    万次阅读 多人点赞 2019-06-18 15:44:47
    深拷贝和浅拷贝区别是什么? 复制一个 Java 对象 浅拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 变量指向堆内存中的对象的指针,不复制堆内存中的对象。 深拷贝:复制基本类型的属性;...
  • 一:浅拷贝 function deep(obj){ var mn = {}; for(var key in obj) { mn[key] = obj[key] } return mn; } var obj0 = deep(obj); //对象 var obj1 = Object.assign({},obj); var {...obj2} = obj; //扩展...
  • js中的深拷贝和浅拷贝与值传递引用传递有着些许的关联,都是根据堆栈中数据的储存来传递数据。 下面主要说一下我对深拷贝和浅拷贝的理解: 简单举个例子来说明;当我们声明一个a变量并且赋值,并且让b等于a,...
  • 深拷贝和浅拷贝区别 JavaScript中有两种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。 最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。 浅拷贝 —-只是拷贝了基本类型的数据...
  • 终于弄清楚JS的深拷贝和浅拷贝了-读这一篇就够了

    万次阅读 多人点赞 2018-07-27 17:22:14
    今天,CVTE面试官问了深拷贝浅拷贝的问题 我的回答是:浅拷贝是拷贝了对象的引用,当原对象发生变化的时候,拷贝对象也跟着变化;...回来查了一下资料,并没有发现面试官说的这种关于深拷贝浅拷贝的说法,看了...
  • python的复制,深拷贝和浅拷贝区别

    万次阅读 多人点赞 2019-06-05 09:43:49
    python的复制,深拷贝和浅拷贝区别 在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用 一般有三种方法, alist=...

空空如也

1 2 3 4 5 ... 20
收藏数 166,389
精华内容 66,555
关键字:

深拷贝