2025年js中深拷贝和浅拷贝的区别(js深拷贝浅拷贝面试怎么说)

js中深拷贝和浅拷贝的区别(js深拷贝浅拷贝面试怎么说)svg xmlns http www w3 org 2000 svg style display none svg

大家好,我是讯享网,很高兴认识大家。



 <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path> </svg> 

讯享网

​ 在前端开发中处理对象和数组时,我们会经常碰到数据拷贝的场景,如果出现一些不恰当的拷贝就会导致一些难以预料的错误发生,难以排查与维护。拷贝又常常分为深拷贝和浅拷贝,要想成为一名优秀的程序员,弄清楚两者的概念及区别尤为重要。

这里举个错误使用拷贝的简单例子:

img
讯享网

​ 我相信大家对于这种表格并不陌生,在表格的操作列中有一个显示详细数据的操作,这里需要实现一个数据回显的功能。

img

​ 如果这里只是简单的通过直接赋值就会出现一个问题,当我在回显数据上使用了修改功能后,哪怕没有选择确定或者保存,外面表格的数据都会相应的发生变化。

这种就是典型的浅拷贝带来的问题,对拷贝数组做出的更改有时会影响到源数组。注意这里的有时,后文会介绍为什么。

  1. 我们先来看个最简单的拷贝:
讯享网

毫无疑问,输出结果是10。

为了深入理解拷贝这一过程,我们需要研究数据在内存中的存储方式。

img
对于上面的基本数据类型(number)的存储,js会采用值类型栈存储的方式,把a的值直接拷贝给b。

值类型栈存储: 主要针对(Number、String、Boolean)三种基本数据类型。直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。

  1. 再看个例子:
 

在这里插入图片描述

对于上面的引用数据类型(Array)的存储,js会采用引用类型堆栈存储的方式,在中存储变量名和引用(地址),在中存储实际数据。拷贝的过程发生在栈中,js会将a的引用直接拷贝给b,这样就导致了两个变量同时指向一个位置,所以之后无论是对a操作还是对b操作实际上操作的都是同一处地方,所以也就发生源对象和拷贝对象互相影响的情况了。

引用类型堆栈存储: 主要针对Object、Array这两种引用数据类型, 它们同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

  1. 我们再看一种情况:

    大家思考一下输出结果是什么

讯享网

答案是:[1,2,[3,4]] 和 [2,2,1]

​ 那如果我修改一下呢

 

答案是: [1,2,[1,4]] 和 [2,2,[1,4]]

相信真正理解了深浅拷贝的同学都能做对,但如果有做错的说明你还没有真正的理解深浅拷贝。

实际上在堆中,嵌套的引用类型仍然是通过存储引用(地址)实现的,而并非直接存储它本身。

在这里插入图片描述

当我们将a使用展开运算符或者其它标准的内置对象复制操作方法拷贝给b时,会开辟一个新的内存空间用于存放b的数据。但是对于a存放的数据,它会原封不动的复制过去,包括a存放的引用,这也就导致了b中的引用数据类型属性会与a共享引用

我们看一下图解:

在这里插入图片描述

那这时候有同学就有疑问了,我明明将b[0]修改了a[0]却没有变,为啥这个还是浅拷贝呢?不是说浅拷贝之后源对象和拷贝对象更改会互相影响吗?这就是我上文提到的钱拷贝的概念,

现在我们引出浅拷贝的定义,这里参考的是MDN上的定义。

对象的浅拷贝是其属性与拷贝源对象的属性共享相同引用(指向相同的底层值)的副本。

注意这里所说的是共享相同引用,对于不是引用类型的数据,自然不会影响到浅拷贝的判定了。

实际上,在 JavaScript 中,所有标准的内置对象复制操作(展开语法、、、、 和 )创建的都是浅拷贝而不是深拷贝。

各位可以自己实践一遍,这里就不再赘述了。

那么如何才算是深拷贝呢?继续使用上面提到的数据例子,如果我们实现了源对象和拷贝对象完全独立,就实现了深拷贝。
在这里插入图片描述

这里引出MDN上关于深拷贝的定义如下:

对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本。

实际上,MDN上的这句话在我看来是有一些歧义的,比如说源对象和拷贝对象的第一层的属性不共享相同引用,但是第一层属性的嵌套属性却共享引用,这种情况实际上并不属于我们常说的深拷贝,它们并没有完全独立。

深拷贝应确保整个对象及其嵌套属性都不共享相同的引用,而不仅仅是第一层属性。至于这句话是否有歧义,欢迎各位小伙伴在评论区留下自己的看法,我们共同探讨。

讯享网

​ 这是最简单有效的方法,但是这种方法存在弊端,想要使用这种方法的前提是数据允许被序列化。

​ 于是对于一些无法序列化的数据自然不能采用这种方法

例如:函数、Symbol、正则表达式、在 HTML DOM API 中表示 HTML 元素的对象、递归数据以及许多其他情况。

 

该方法与JSON序列化类似,但常用于操作定型数组,它们有以下几个不同点:

  • 复杂数据类型支持: 支持复制包括 DOM 节点、Blob、File、ArrayBuffer 等复杂数据类型,而 JSON 序列化只支持基本的 JavaScript 数据类型。
  • 用途不同: 主要用于在 Web Workers、postMessage() 等场景中复制包含复杂数据的对象,而 JSON 序列化主要用于简单数据的转换和传输。
  • 兼容性: 可能不适用于所有 JavaScript 环境,而 JSON 序列化是一种通用且普遍支持的数据序列化方式。
讯享网
 
讯享网
 

这个实现了,以及对函数的拷贝,但是并不能解决循环引用(会发生栈溢出)的问题

实现对循环引用的拷贝:

讯享网
 

改良之后的深拷贝能够实现循环引用的拷贝,但是对于symbol等还是不支持,除此以外还需要考虑原型上的内容是否实现深拷贝,还有对Map、Set、定型数组等等的考虑,所以最好的办法还是根据自己的需求选择最合适的方式,这里锻炼的是一个递归的思想。

  • MDN
  • 详细分析讲解JS深拷贝和浅拷贝
  • 深拷贝的终极探索


小讯
上一篇 2025-06-09 23:26
下一篇 2025-06-14 09:57

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/206267.html