彻底讲明白浅拷贝与深拷贝

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底讲明白浅拷贝与深拷贝相关的知识,希望对你有一定的参考价值。

参考技术A

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。

1、基本数据类型的特点:直接存储在栈(stack)中的数据

2、引用数据类型的特点: 存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的

深拷贝和浅拷贝的示意图大致如下:

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

当我们把一个对象赋值给一个新的变量时, 赋的其实是该对象的在栈中的地址,而不是堆中的数据 。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

浅拷贝是按位拷贝对象, 它会创建一个新对象 ,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

我们先来看两个例子,对比赋值与浅拷贝会对原对象带来哪些改变?

上面例子中,obj1是原始数据,obj2是赋值操作得到,而obj3浅拷贝得到。我们可以很清晰看到对原始数据的影响,具体请看下表:

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

注意:当object只有一层的时候,是深拷贝

修改新对象会改到原对象:

同样修改新对象会改到原对象:

关于Array的slice和concat方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

原数组的元素会按照下述规则拷贝:

可能这段话晦涩难懂,我们举个例子,将上面的例子小作修改:

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。

这是因为 JSON.stringify() 方法是将一个javascript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。

递归方法实现深度克隆原理: 遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

该函数库也有提供 _.cloneDeep 用来做 Deep Copy。

阅读原文

看完文章留完言,还有福利拿,往下看👇👇👇
感兴趣的小伙伴可以在公号【grain先森】后台回复【190313】获取HTML5详解、CSS3详解和Vue详解及实战项目,可以转发朋友圈和你的朋友分享哦。

python list.copy浅拷贝与深拷贝[重复]

【中文标题】python list.copy浅拷贝与深拷贝[重复]【英文标题】:python list.copy shallow vs deep copy [duplicate] 【发布时间】:2017-10-14 11:58:54 【问题描述】:

也许我不明白浅拷贝的定义……但我很困惑:

来自文档:

其中“s”是一个列表(但同样的问题分别适用于字典)。

"s.copy() | 创建 s 的浅拷贝(与 s[:] 相同)"

除了我认为s[:] 是一个深拷贝。例如,请参阅this stack overflow answer 了解如何复制列表(不只是指向原始版本)。并且使用 list1.copy() 似乎可以进行深层复制,也就是与 [:]

相同的行为
l1 = [1,2,3,4]
l2 = l1[:]
l3 = l1.copy()



l2.append(5)
l3[0] = 99
print(l1)
print(l2)
print(l3)
>> [1,2,3,4]
>> [1,2,3,4,5]
>> [99,2,3,4]

看起来l1l2l3 都是单独的 对象。我错过了什么?

【问题讨论】:

...如果我将 l1 更改为数字列表列表,我仍然会得到相同的结果 - l1.copy() 似乎创建了一个单独的对象 没有。这些都是浅拷贝。该问题的区别在于单纯的分配(根本不复制)和复制(浅的) @juanpa.arrivillaga, Ahhhh 好的,我明白了。 @RSHAP 你可以用is 测试深度相等,用== 测试浅相等。在这里,深层平等意味着浅层平等。 (l1 is l2 仅是 True,如果两个引用都指向同一个对象,而 l1 == l2True,当 l1l2 的所有值都相等时) 【参考方案1】:

您只是误解了在这种情况下“浅”和“深”的含义。

浅拷贝只是顶层元素的拷贝。如果这些元素中的任何一个本身就是列表,则副本仍将引用原始列表。 l1[:]l1.copy() 都是这样做的。

深拷贝是所有级别的拷贝。如果任何元素是列表,它们也将被深度复制。不会共享任何参考。这就是copy.deepcopy() 所做的。

【讨论】:

【参考方案2】:

浅拷贝意味着新列表包含对与旧列表相同的对象的引用。

例如:

foo = [1, 2, []]
bar = foo.copy()
bar[-1].append(3)
print(foo)

我们将看到bar 中对象的突变也会“污染”foo

如果我们使用深拷贝重做这个例子,情况就不同了:

import copy
foo = [1, 2, []]
bar = copy.deepcopy(foo)
bar[-1].append(3)
print(foo)

这是因为深拷贝会创建列表的新(深拷贝),而不是仅仅复制对旧列表的引用。

【讨论】:

以上是关于彻底讲明白浅拷贝与深拷贝的主要内容,如果未能解决你的问题,请参考以下文章

浅拷贝与深拷贝

C++:浅拷贝与深拷贝

C++:浅拷贝与深拷贝

实现浅拷贝与深拷贝

实现浅拷贝与深拷贝

Java专题十九:浅拷贝与深拷贝