news 2026/3/27 18:45:55

前端高频面试题:深拷贝和浅拷贝的区别?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端高频面试题:深拷贝和浅拷贝的区别?

前言

对于前端同学来说,对象的深拷贝和浅拷贝可以说是面试中最火热的题目之一了,今天我们一起来把它盘明白。

1、深拷贝和浅拷贝介绍

深拷贝和浅拷贝都是对对象进行拷贝,其主要区别是,在对象拷贝时,对引用数据类型的处理方式。

1.1 浅拷贝(Shallow Copy)

浅拷贝是指只复制对象本身和对象中的基本数据类型,对于引用类型的属性,只复制内存地址引用,不复制引用的对象本身

浅拷贝的特点:

  • 只拷贝第一层。
  • 对于基本数据类型,拷贝值。
  • 对于引用数据类型,拷贝引用,即新对象和原对象共享引用类型的属性。

1.2 深拷贝(Deep Copy)

深拷贝是指创建一个对象,这个对象的内容和原始对象完全相同,但它们是存储在不同的内存地址上的,这意味着,我们修改新对象,原始对象不受影响

深拷贝的特点:

  • 拷贝所有层级,对多层的属性进行循环递归拷贝。
  • 对于基本数据类型,拷贝值。(和浅拷贝相同)
  • 对于引用数据类型,创建一个新的引用对象,并循环拷贝到新对象中,也就是新对象和原对象分别引用独立的引用数据,不共享引用。

1.3 示例说明

举个例子:

constobj={a:1,b:[1,2,3],c:{d:4,e:5,}}constnewObj1=shallowClone(obj);console.log('newObj1:',newObj1);constnewObj2=deepClone(obj);console.log('newObj2:',newObj2);/** 打印结果: newObj1: { a: 1, b: [ 1, 2, 3 ], c: { d: 4, e: 5 } } newObj2: { a: 1, b: [ 1, 2, 3 ], c: { d: 4, e: 5 } } */

我这里并没有给出shallowClonedeepClone的实现代码,后文会分别详细介绍。

从打印结果来看,newObj1newObj2都是相同的,而不同的是,对于浅拷贝来说:

  • obj.b === newObj1.b,结果为true
  • obj.c === newObj1.c,结果为true

而对于深拷贝来说,它们前后都是不相等的。

  • obj.b === newObj.b,结果为false
  • obj.c === newObj.c,结果为false

2、浅拷贝的实现方式

2.1 展开运算符

可以用ECMAScript 2015新增特性,也就是ES6提供的语法三个点(...)展开运算符,它可以展开可迭代对象(数组、字符串、Map、Set 等),从而来实现浅拷贝,具体用法如下:

constnewObj={...obj};

2.2 Object.assign

Object.assignObject类自带的一个静态方法,可以将一个或多个对象中的可枚举(此属性的enumerable为true)自有属性(对象自身的,从原型上继承的不算)合并到目标对象中。

constnewObj=Object.assign({},obj1, obj2,obj3,...objN);

2.3 for…in + Object.prototype.hasOwnProperty

直接用for..in循环,配合Object.prototype.hasOwnProperty判断是否是自身的属性来进行拷贝。

functionshallowClone(obj){constnewObj={};for(letkeyinobj){if(obj.hasOwnProperty(key)){newObj[key]=obj[key];}}returnnewObj;}constobj={a:1,b:[1,2,3],c:{d:4,e:5,}};constnewObj=shallowClone(obj);console.log(newObj)

或者用Object.keys先拿到自身的属性的数组,然后forEach循环拷贝也可,方式有很多种,大家可以自行扩展。

2.4 扩展:展开运算符和Object.assign在实现浅拷贝上有什么区别?

展开运算符和Object.assign虽然可以都实现浅拷贝,但仍有细微的区别,比如在遇到gettersetter时,两者的表现不一样。来举个例子:

首先是展开运算符:

constobj1={geta(){console.log('getter')return1;},seta(val){console.log('setter')}}constobj2={a:2,}constnewObj1={...obj1,...obj2};console.log(newObj1);console.log(newObj1.a)console.log('------------------------');constnewObj2={...obj2,...obj1};console.log(newObj2);console.log(newObj2.a)/** * 打印结果: getter { a: 2 } 2 ------------------------ getter { a: 1 } 1 */

从打印结果可以看出,展开运算符在拷贝时有如下特点:

  1. 合并时不会执行setter
  2. 合并后取值时会按照合并的先后顺序,后合并的值优先级更高

然后是Object.assign

constobj1={geta(){console.log('getter')return1;},seta(val){console.log('setter')}}constobj2={a:2,}constnewObj=Object.assign(obj1,obj2);console.log(newObj);console.log(newObj.a)console.log('------------------------');constnewObj1=Object.assign(obj2,obj1);console.log(newObj1);console.log(newObj1.a);/** * 打印结果: setter { a: [Getter/Setter] } getter 1 ------------------------ getter { a: 1 } 1 */

从打印结果可以看出,``Object.assign`在拷贝时有如下特点:

  1. 同名属性和同名gettersetter合并时,会执行setter,而同名gettersetter同名属性合并时却不会执行setter
  2. 无论同名属性和同名gettersetter的合并先后顺序如何,最终访问只会访问到getter里面的值,只是从控制台里看的效果不一样而已。

3、深拷贝的实现方式

3.1 JSON.parse(JSON.stringify(obj))

  • JSON.stringify:将一个对象序列化成一个JSON字符串,包括嵌套的对象属性。
  • JSON.parse:将一个JSON反序列化成为一个 JS 对象。

由于在内存中JSON 字符串的地址都是独立的,和原始对象不是同一个地址,所以我们就能通过JSON.parse解析出一个新对象了。

下面看一下用这种方式实现深拷贝的优缺点:

优点:

  1. 简单易用:语法JSON.parse(JSON.stringify(obj)),用起来非常简单。
  2. 跨平台:在不同平台和环境都能用,甚至是其它语言也有提供对应的实现,比如Java
  3. 兼容性好:各大浏览器都支持。

缺点:

  1. 无法处理特殊对象类型,比如函数、正则表达式、日期对象等
    • 拷贝的时候会丢失函数和 undefined。
    • 时间对象 Date 会变成字符串形式。
    • RegExp、Error 对象会变成空对象。
    • NaN、Infinity、-Infinity会变成 null。
    • 等等…。
  2. 无法处理循环引用,比如在一个对象中,a引用了b,b引用了c,而c又引用了a,出现这种情况调用JSON.parse(JSON.stringify(obj))会报错。

3.2 借助第三方库

实现深拷贝,一般我们会借助第三方库实现,比如lodash,lodash提供了一个cloneDeep的方法实现深拷贝。

const_=require('lodash');constobj={a:[{b:2}]};constres=_.cloneDeep(obj);console.log(res);// 输出:{ a: [ { b: 2 } ] }

3.3 手撸一个深拷贝方法

深拷贝其实实现起来要写完整,还是挺复杂的,要处理函数数组正则,甚至是symbolbuffer等,但对于面试来说,我们写个简单版本就行啦。

手动实现深拷贝有两个关键点:

  • 对象是以keyvalue键值对的方式存储的,所以要拷贝它们必须要用循环
  • 既然要深拷贝,相较于只拷贝最外层浅拷贝,就需要用递归或循环拷贝N层

废话不多说,直接上完整代码。

constisObj=(target)=>typeoftarget==='object'&&target!==nullfunctiondeepClone(obj,hash=newWeakMap()){if(!isObj(obj))returnobjif(hash.has(obj))returnhas.get(obj)consttarget=newobj.constructor()hash.set(obj,target)Object.keys(obj).forEach((key)=>{target[key]=deepClone(obj[key],hash)})returntarget}constobj={a:[{b:2}]}constres=deepClone(obj)console.log(res)// 输出:{ a: [ { b: 2 } ] }

我们用一个WeakMap来处理循环引用,然后通过拿到对象引用的constructor来复制对象,这样我们就省去了判断不同对象类型这一步,会简单很多,然后forEach循环递归复制就好啦。

虽然可能有人会说用constructor比较粗糙,但这是比较简洁的写法,我们面试的时候大可不必这么较真,只要知道它的核心思路和原理就行啦!

4、如何选择浅拷贝和深拷贝?

浅拷贝能共享数据,节约内存,性能高

深拷贝实现数据隔离,数据更安全。但要注意循环引用问题特殊对象的处理以及对象层级过深带来的性能问题

在平常开发中,数据拷贝几乎设计不到性能问题,所以如果不介意引用数据共享,选浅拷贝,需要引用数据相互独立,选深拷贝。

小结

  1. 先介绍了深拷贝深拷贝和浅拷贝的区别。它们的主要区别在于拷贝时对引用数据类型的处理,浅拷贝是共享引用,而深拷贝是复制出一个新对象,和原对象相互独立,没任何关系。

  2. 然后介绍了浅拷贝和深拷贝的主要实现方式。浅拷贝的实现方式主要有展开运算符Object.assignfor..in + Object.prototype.hasOwnProperty等,还扩展介绍了展开运算符和Object.assign的区别,主要是体现在复制时对属性访问器gettersetter的处理方式不同,而深拷贝的实现方式主要有JSON.parse(JSON.stringify(obj))借助第三方库,比如 lodash手撸一个深拷贝方法(注意对循环引用的处理)

  3. 最后介绍了在实际开发中如何选择浅拷贝和深拷贝。主要选择方式是,不介意引用数据共享,选浅拷贝,需要引用数据相互独立,选深拷贝。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 7:34:35

vue和springboot框架开发的同人小说创作与在线阅读分享平台系统_ljwg4kit

文章目录具体实现截图主要技术与实现手段关于我本系统开发思路java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式!具体实现截图 同行可拿货,招校园代理 vuespringboot_ljwg4kit 框架开发的同人小说创作与…

作者头像 李华
网站建设 2026/3/24 9:44:55

vue和springboot框架开发的图书馆座位预约微信小程序系统_7mg5c898

文章目录具体实现截图主要技术与实现手段关于我本系统开发思路java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式!具体实现截图 同行可拿货,招校园代理 vuespringboot_7mg5c898 框架开发的图书馆座位预约…

作者头像 李华
网站建设 2026/3/26 5:12:07

vue和springboot框架开发的问卷调查管理系统_xvc14u58

文章目录 具体实现截图主要技术与实现手段关于我本系统开发思路java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式! 具体实现截图 同行可拿货,招校园代理 vuespringboot_xvc14u58 框架开发的问卷调查管理系…

作者头像 李华
网站建设 2026/3/17 22:31:37

AI玩具爆发:情感陪伴的“下一代硬件”,还是高毛利的智商税?

【摘要】AI玩具市场在需求与资本双重驱动下急速膨胀,但产品体验普遍与营销宣传脱节。技术瓶颈、商业模式争议及安全合规风险,共同构成了行业当前的核心挑战。引言2025年,AI玩具赛道呈现出一种矛盾的繁荣。一方面,市场数据极为亮眼…

作者头像 李华
网站建设 2026/3/24 23:26:04

python_django求职招聘岗位信息分析系统的设计与实现_xz0yin70可视化大屏

文章目录系统截图项目技术简介可行性分析主要运用技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式!系统截图 python_django求职招聘岗位信息分析系统的设计与实现_xz0yin70可视化大屏 项目技术简介 P…

作者头像 李华
网站建设 2026/3/25 1:59:35

测试资源的优化配置

在当今快速迭代的软件开发生命周期中,测试资源的优化配置已成为保障产品质量、降低项目风险的核心环节。软件测试从业者经常面临资源稀缺的挑战,如有限的时间、预算和人力,这些因素直接影响到测试覆盖率和缺陷检测能力。根据最新行业调查&…

作者头像 李华