前言
前端开发中,关于JS原生的原理使用是前端开发者的看家本领,尤其是关于底层和原理的掌握使用,甚为重要。而且编程语言有一些比较共性的概念在不同的编程语言中会有相同的概念,比如深拷贝和浅拷贝它们不仅在JS中有,在其他编程语言中也经常被提及到,而且在实际开发过程中也常常需要区分当前使用的到底是浅拷贝还是深拷贝,如果没有区分正确,在该需要使用深拷贝的时候使用了浅拷贝,那就容易埋下bug隐患,而且不易排查出来。在JS中深拷贝和浅拷贝的使用是比较常用的知识点,而且在前端求职面试的时候二者也是必考知识点,可以说非常重要,那么本文就来做一下总结,方便查阅使用。
JS数据类型
要谈浅拷贝和深拷贝之前,前提是要先聊聊JS的数据类型,众所周知,JS数据类型分为:基本数据类型和引用数据类型。基本数据类型包含number、string、boolean、Null、undefined和ES6语法的Symbol等,它们是存储在程序等栈内存中;引用数据类型包含Array、Object、function(函数)以及ES6语法的Set和Map等,它们是将其地址存储在程序的栈内存中,但真实数据存储在程序的堆内存中。另外,程序的内存又被分为栈内存和堆内存,不管是number、string、boolean、Null、undefined和ES6语法的Symbol还是Array、Object、function(函数)都会被存储在内存中。具体如图所示:
需要明确的一点:深拷贝和浅拷贝的概念只适用于Object(对象)或者Array(数组)等引用数据类型。
深拷贝与浅拷贝概念
浅拷贝:只拷贝数据在程序中的内存地址,而不是在程序内存中重新创建一个一模一样的Object(对象)或者Array(数组)。换句话说,浅拷贝是创建一个新对象,该对象有着原始对象属性值的一份完整的拷贝。若属性是基本类型,拷贝的就是基本类型的值;若属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:会在程序内存中开辟一个新的存储空间,拷贝一个一模一样的Object(对象)或者Array(数组)。换句话说,深拷贝是将一个对象从内存中完整的拷贝一份出来,从程序的堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原来的对象。
深拷贝与浅拷贝的区别
浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。
本质区别:是否真正获取一个对象的复制实体,而不是引用。深拷贝是新开辟一个新的地址空间,对象的改变不会影响原来的数组;浅拷贝只是复制原来的对象,指针仍然指向原来的数组,当前数组变化的时候会触发原来数组发生改变。
浅拷贝
根据上面浅拷贝的定义来讲,浅拷贝复制了对象(数组)存储在程序栈内存中的地址,而不是在程序内存中重新开辟一个新的存储空间用于存储新的对象,也可以说是两个对象共用一份内容。这里来举一个例子说明,浅拷贝示例如下所示:
上面的实例可得,如果object_1的内容发生改变时,object_2的内容也会跟着改变。原因就是:object_2拷贝了object_1在栈内存中的地址,也就是object_2和object_1共用存储在堆内存的数据;同理得,当object_2发生改变的时候,object_1也会随之改变。
深拷贝
同样根据上面的定义释义可知,深拷贝与浅拷贝最大不同是:浅拷贝只会拷贝栈内存中的数据地址,深拷贝会在内存中重新开辟一段新的存储空间,使得两个对象(数组)指向两个不同的堆内存数据,从而实现改变互不影响。这里来举一个例子说明,深拷贝示例如下所示:
上面的实例可得,object_3是object_1的深拷贝对象,所以object_3不会随着object_1的改变而改变;同理得,当object_3发生改变的时候,object_1也不会随之改变。
深拷贝与浅拷贝的实现方式
浅拷贝的实现方式有
- Array.concat( );
- Array.slice( );
- Object.assign( ),它可以把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
语法:Object.assign(target, source1, source2)
参数:target表示目标对象;source表示源对象
示例:
4、扩展运算符,利用扩展运算符可以在构造字面量对象时进行克隆或者属性拷贝。
语法:let cloneObject = { ...object };
示例:
注意:扩展运算符与Object.assign()有同样的缺陷,那就是对于值是对象的属性无法完全拷贝成2个不同对象,但如果属性都是基本类型的值的时候,使用扩展运算符会更加方便。
深拷贝的实现方式有
- JSON.parse(JSON.stringify());
- jQuery.extend( );
语法:$.extend( [deep ], target, object1 [, objectN ] )
参数:deep为Boolean类型,指示是否深度合并对象,默认为false。若该值为true,且多个对象的某个同名属性也都是对象,则该"属性对象"的属性也将进行合并操作。
深拷贝与浅拷贝的应用场景
在JS中,不管是浅拷贝还是深拷贝,通常用于操作Object 或 Array等引用类型。如果实际开发中,只涉及一层结构的Array和Object拷贝一个副本使用的时候,使用浅拷贝;如果实际开发中,想对某个数组或对象的值进行修改,但又要保留原数组或原对象的值不被修改,这就需要使用深拷贝来创建一个新的数组或对象,进而达到在操作新的数组或对象时,保持原数组或对象不变。
拓展:递归实现完全拷贝
通过使用递归实现完全拷贝,具体示例代码如下所示:
上面代码通过递归实现完全拷贝,而且没有舍弃函数,只是拷贝了函数的指针,符合实际业务开发需求,针对一个功能只写一个函数,可以反复调用即可。深拷贝只针对于引用数据类型,在使用过程中需要根据具体的使用场景选择不同的拷贝方式,若对象和数组中没有引用数据类型可以使用扩展运算符,更加方便。完全拷贝,一般不建议使用json的方法,因为会舍弃函数,可以直接封装一个函数使用递归来完全拷贝。
特别注意:函数只拷贝它的指针,可以根据符合相同功能一个函数的设计思想来操作。
最后
通过本文关于前端开发JS中深拷贝和浅拷贝的区别的详细介绍,深拷贝和浅拷贝不管是在实际的前端开发工作中还是在前端求职面试中都是非常关键的知识点,所以作为前端开发者来说必须要掌握它相关的内容,尤其是从事前端开发不久的开发者来说尤为重要,是一篇值得阅读的文章,重要性就不在赘述。欢迎关注,一起交流,共同进步。