前端开发:JS中深拷贝和浅拷贝的区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端开发:JS中深拷贝和浅拷贝的区别相关的知识,希望对你有一定的参考价值。


前言

前端开发中,关于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(函数)都会被存储在内存中。具体如图所示:

前端开发:JS中深拷贝和浅拷贝的区别_javascript

需要明确的一点:深拷贝和浅拷贝的概念只适用于Object(对象)或者Array(数组)等引用数据类型。

深拷贝与浅拷贝概念

浅拷贝:只拷贝数据在程序中的内存地址,而不是在程序内存中重新创建一个一模一样的Object(对象)或者Array(数组)。换句话说,浅拷贝是创建一个新对象,该对象有着原始对象属性值的一份完整的拷贝。若属性是基本类型,拷贝的就是基本类型的值;若属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝:会在程序内存中开辟一个新的存储空间,拷贝一个一模一样的Object(对象)或者Array(数组)。换句话说,深拷贝是将一个对象从内存中完整的拷贝一份出来,从程序的堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原来的对象。

深拷贝与浅拷贝的区别

浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。

深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。

浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。

本质区别:是否真正获取一个对象的复制实体,而不是引用。深拷贝是新开辟一个新的地址空间,对象的改变不会影响原来的数组;浅拷贝只是复制原来的对象,指针仍然指向原来的数组,当前数组变化的时候会触发原来数组发生改变。

浅拷贝

根据上面浅拷贝的定义来讲,浅拷贝复制了对象(数组)存储在程序栈内存中的地址,而不是在程序内存中重新开辟一个新的存储空间用于存储新的对象,也可以说是两个对象共用一份内容。这里来举一个例子说明,浅拷贝示例如下所示:

let object_1 = 

a:1,

b:2



console.log(object_1); //输出结果为:a:1,b:2

let object_2 = object_1; //具有代表性的浅拷贝操作

console.log(object_2); //输出结果为:a:1,b:2

object_1.a = 10; //修改原对象中的a属性值

console.log(object_2); //由于公用同一份内容,所以属性a的值一起跟着改变,输出结果为:a:10,b2

前端开发:JS中深拷贝和浅拷贝的区别_前端_02

 

上面的实例可得,如果object_1的内容发生改变时,object_2的内容也会跟着改变。原因就是:object_2拷贝了object_1在栈内存中的地址,也就是object_2和object_1共用存储在堆内存的数据;同理得,当object_2发生改变的时候,object_1也会随之改变。

深拷贝

同样根据上面的定义释义可知,深拷贝与浅拷贝最大不同是:浅拷贝只会拷贝栈内存中的数据地址,深拷贝会在内存中重新开辟一段新的存储空间,使得两个对象(数组)指向两个不同的堆内存数据,从而实现改变互不影响。这里来举一个例子说明,深拷贝示例如下所示:

let object_1 = 

a:1,

b:2



console.log(object_1); //输出结果为:a:1,b:2

//在JS中使用JSON的stringify和parse就是深拷贝的一种方式。注意:该方法使用会有问题,实际开发中不建议使用,这里代码只做示例使用

let object_2 = JSON.stringify(object_1);

let object_3 = JSON.parse(object_2);

console.log(object_3); //输出结果为:a:1,b:2

object_1.a = 10; //修改原对象中的a属性值

console.log(object_3); //由于object_3属于新创建的存储空间,所以不受object_1的影响,输出结果仍然为:a:1,b:2

前端开发:JS中深拷贝和浅拷贝的区别_前端_03

上面的实例可得,object_3是object_1的深拷贝对象,所以object_3不会随着object_1的改变而改变;同理得,当object_3发生改变的时候,object_1也不会随之改变。

深拷贝与浅拷贝的实现方式

浅拷贝的实现方式有

  1. Array.concat( );
  2. Array.slice( );
  3. Object.assign( ),它可以把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

语法:Object.assign(target, source1, source2)

参数:target表示目标对象;source表示源对象

示例:

let object_1 = 

a: 1,

b: f: g: 1 ,

c: [ 1, 2, 3 ]

;

let object_2 = Object.assign(, object_1);

console.log(object_2.b.f === object_1.b.f); //输出结果为:true

4、扩展运算符,利用扩展运算符可以在构造字面量对象时进行克隆或者属性拷贝。

语法:let cloneObject = ...object ;

示例:

var object = a:1,b:c:1

var object2 = ...object;

object.a=2;

console.log(object); //输出结果为:a:2,b:c:1

console.log(object2); //输出结果为:a:1,b:c:1

object.b.c = 2;

console.log(object); //输出结果为:a:2,b:c:2

console.log(object2); //输出结果为:a:1,b:c:2

注意:扩展运算符与Object.assign()有同样的缺陷,那就是对于值是对象的属性无法完全拷贝成2个不同对象,但如果属性都是基本类型的值的时候,使用扩展运算符会更加方便。

深拷贝的实现方式有

  1. JSON.parse(JSON.stringify());
  2. jQuery.extend( );

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

参数:deep为Boolean类型,指示是否深度合并对象,默认为false。若该值为true,且多个对象的某个同名属性也都是对象,则该"属性对象"的属性也将进行合并操作。

深拷贝与浅拷贝的应用场景

在JS中,不管是浅拷贝还是深拷贝,通常用于操作Object 或 Array等引用类型。如果实际开发中,只涉及一层结构的Array和Object拷贝一个副本使用的时候,使用浅拷贝;如果实际开发中,想对某个数组或对象的值进行修改,但又要保留原数组或原对象的值不被修改,这就需要使用深拷贝来创建一个新的数组或对象,进而达到在操作新的数组或对象时,保持原数组或对象不变。

拓展:递归实现完全拷贝

通过使用递归实现完全拷贝,具体示例代码如下所示:

function deepCopy(x) 

var newX = Array.isArray(x) ? [] : ;

for (key in x)

// 判断是否为对象

if (typeof x[key] === object && x[key])

// 拷贝下面一层

newX[key] = deepCopy(x[key]);

else

newX[key] = x[key];





return newX;



let object =

a: 2, c: 3,

d() console.log(111111) ,

r: a: 2



let object1 = deepCopy(object)

console.log(object1.d === object1.d)

前端开发:JS中深拷贝和浅拷贝的区别_浅拷贝_04

上面代码通过递归实现完全拷贝,而且没有舍弃函数,只是拷贝了函数的指针,符合实际业务开发需求,针对一个功能只写一个函数,可以反复调用即可。深拷贝只针对于引用数据类型,在使用过程中需要根据具体的使用场景选择不同的拷贝方式,若对象和数组中没有引用数据类型可以使用扩展运算符,更加方便。完全拷贝,一般不建议使用json的方法,因为会舍弃函数,可以直接封装一个函数使用递归来完全拷贝。

特别注意:函数只拷贝它的指针,可以根据符合相同功能一个函数的设计思想来操作。

最后

通过本文关于前端开发JS中深拷贝和浅拷贝的区别的详细介绍,深拷贝和浅拷贝不管是在实际的前端开发工作中还是在前端求职面试中都是非常关键的知识点,所以作为前端开发者来说必须要掌握它相关的内容,尤其是从事前端开发不久的开发者来说尤为重要,是一篇值得阅读的文章,重要性就不在赘述。欢迎关注,一起交流,共同进步。

JS中深拷贝和浅拷贝记录及解决方法

 这是本人第一次写博客。。。好紧张,有什么固定格式麽,需要爆照麽。。怎样才能让自己表现的不是第一次啊。。

不多说,最近一不小心就跳入一个坑,也是怪我自己知识点扩展不够。。这次记录下,上代码

/*a 原来的数组
* b 复制后的新数组
* */
function copy(a,b) {
for(var i=0;i<a.length;i++)
{
b.push(a[i])
}
}
var arr1=[1,2,3,4,5];
var arr2=[];
copy(arr1,arr2);
console.log(arr2);

输出结果:

技术分享

可以看到,这里的arr2和arr1现在一样,现在如果改变arr2中的某一个值,看看会不会影响到arr1

arr2[0]=100;
console.log(arr2)

输出结果:

技术分享

从结果可以看出,虽然改变了arr2但是arr1数组的值没有改变。
因为这里是基本数据类型,存于栈中。复制的时候,直接复制的是arr1的值给arr2.所以就算改变了arr2,arr1也不会改变的。但是如果arr1中含有对象呢。那么问题来了,这就是我进的坑
 
再来个栗子:
 
var arr3=[{name:‘张三‘},{age:22},{sex:‘男‘}];
var arr4=[];
copy(arr3,arr4);//复制arr3的值
console.log(arr3);
console.log(arr4);
 
输出结果为:
 

技术分享

上图是复制arr3到arr4中的结果,下图改变arr4[0].name=‘李四‘的结果
 
技术分享
 
此时,我发现我修改的是arr4的数组的值,为什么arr3的值也会改变呢,后来查了些资料发现  (以下为百度的资料)
 
浅拷贝
   浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

 

技术分享

 

 在图中,SourceObject有一个int类型的属性 "field1"和一个引用类型属性"refObj"(引用ContainedObject类型的对象)。当对SourceObject做浅拷贝时,创建了CopiedObject,
它有一个包含"field1"拷贝值的属性"field2"以及仍指向refObj本身的引用。由于"field1"是基本类型,所以只是将它的值拷贝给"field2",但是由于"refObj"是一个引用类型,
所以CopiedObject指向"refObj"相同的地址。因此对SourceObject中的"refObj"所做的任何改变都会影响到CopiedObject。

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

技术分享

在上图中,SourceObject有一个int类型的属性 "field1"和一个引用类型属性"refObj1"(引用ContainedObject类型的对象)。当对SourceObject做深拷贝时,创建了CopiedObject,它有一个包含"field1"拷贝值的属性"field2"以及包含"refObj1"拷贝值的引用类型属性"refObj2" 。因此对SourceObject中的"refObj"所做的任何改变都不会影响到CopiedObject


解决办法:

1.对象只有一层的话可以使用 Object.assign()函数;
var person1={name:‘小王‘,age:22,sex:‘男‘}
var person2=Object.assign({},person1);
person2.name=‘小李‘;
console.log(person1);
console.log(person2);
输出结果:

技术分享

2.转成 JSON 再转回来

JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

var arr3=[{name:‘张三‘},{age:22},{sex:‘男‘}];
var arr4=JSON.parse(JSON.stringify(arr3));
arr4[0].name=‘李四‘;
console.log(arr3);
console.log(arr4);

输出结果:

技术分享

 

 

以上是简单的方法,这里记录了更多的方法。

http://www.cnblogs.com/Chen-XiaoJun/p/6217373.html

第一次写,喷吧。。轻点儿。。














































以上是关于前端开发:JS中深拷贝和浅拷贝的区别的主要内容,如果未能解决你的问题,请参考以下文章

java中深克隆与浅克隆的区别

c++中深拷贝和浅拷贝问题

C++中深拷贝与浅拷贝

浅析Python中深拷贝和浅拷贝

python的复制,深拷贝和浅拷贝的区别

如何实现数组深拷贝和浅拷贝?