资讯专栏INFORMATION COLUMN

javascript的深拷贝VS浅拷贝

Nekron / 2623人阅读

摘要:深拷贝浅拷贝本文主要对深拷贝浅拷贝的解释及实现做一下简单记录。之所以会有深拷贝与浅拷贝之分,是因为不同数据类型的数据在内存中的存储区域不一样。但注意,只能做一层属性的浅拷贝。

深拷贝VS浅拷贝

本文主要对深拷贝&浅拷贝的解释及实现做一下简单记录。原文链接,欢迎star。

之所以会有深拷贝与浅拷贝之分,是因为不同数据类型的数据在内存中的存储区域不一样。

堆和栈是计算机中划分出来用来存储的区域,其中堆(heap)则是动态分配的内存,大小不定也不会自动释放;而栈(stack)为自动分配的内存空间,它由系统自动释放。存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。

众所周知,JavaScript中的数据分为(基本类型和引用类型)。五种基本类型(boolean,number,undefined,null,string,)的数据的原始值是大小确定不可变的,所以放在栈内存中;而引用类型(object)是放在堆内存中的,对应赋值的变量只是一个存放在栈内存中的指针,也就是一个指向引用类型存放在堆内存中的地址。

引用类型比较与基本类型比较的区别

引用类型是引用的比较,例如

</>复制代码

  1. // a与b是两个引用类型,但其指针指向的是两个不同的引用
  2. let a = [1,2,3];
  3. let b = [1,2,3];
  4. a===b; //false
  5. // 引用类型的赋值操作,b的指针也是指向与a同样的引用
  6. let a = [1,2,3];
  7. let b = a;
  8. a===b; //true

而基本类型间的比较是值的比较,例如

</>复制代码

  1. let a = 1;
  2. let b = 1;
  3. a===b; //true
  4. let a = 1;
  5. let b = a;
  6. a===b; //true
赋值操作:传值与传址的区别

在对基本类型进行赋值操作的时候实际是传值,即在栈内存中新开一个区域给新的变量,然后再把值赋给它。所以基本类型赋值的变量是两个互相独立变量。

</>复制代码

  1. let a = 10;
  2. let b = a;
  3. b++;
  4. console.log(a); //10
  5. console.log(b); //11

而引用类型的赋值操作是传址,只是在栈内存中新增了一个变量,同时赋值给这个变量的只是保存在堆内存中对象的地址,也就是指针的指向。因此这两个变量的指针指向同一个地址,相互操作也就会有影响。

</>复制代码

  1. let a = {};
  2. let b = a;
  3. a.name = "slevin";
  4. console.log(b.name); //"slevin"
  5. console.log(a.name); //"slevin"
  6. console.log(a===b); //true

需要注意的是,引用类型的赋值并不算是浅拷贝,因为赋值操作只相当于是引用,两个变量还是指向同一对象,任何一方改变了都会影响到另一方;但浅拷贝出来的变量与源变量已经不同,在不包含子对象的情况下(此情况即为深拷贝),一方改变不会影响到另一方。如下赋值操作:

</>复制代码

  1. let a = {};
  2. let b = a;
  3. b.name = "slevin";
  4. console.log(a.name); //"slevin";对b操作影响到了a
  5. console.log(a===b); //true;因为两者指向同一个对象
浅拷贝实现方法

自定义实现方法:

</>复制代码

  1. // 浅拷贝的方法
  2. function shallowCopy(srcObj) {
  3. let copy = {};
  4. for (let key in srcObj) {
  5. //只拷贝自身的属性,__proto__上继承来的属性不做拷贝,也可去掉这个限制
  6. if (srcObj.hasOwnProperty(key)) {
  7. copy[key] = srcObj[key];
  8. }
  9. }
  10. return copy;
  11. }
  12. let obj = {
  13. name: "slevin",
  14. age: 18,
  15. language: {
  16. "english": "good",
  17. "mandarin": "wonderful",
  18. }
  19. }
  20. let copy = shallowCopy(obj);
  21. copy.age = 28;
  22. console.log(obj.age); // 18; 并没有改变源对象
  23. console.log(obj === copy); //false;两者指向不是同一个对象

利用Object.assign(target,...sources)可将一个或多个源对象上可枚举属性的值复制到目标对象,并返回目标对象。但注意,只能做一层属性的浅拷贝

</>复制代码

  1. let obj = {
  2. name: "slevin",
  3. age: 18,
  4. language: {
  5. english: "good",
  6. mandarin: "wonderful",
  7. test: [1, 2, 3]
  8. },
  9. fn:function(){
  10. console.log("this:",this.name);
  11. }
  12. }
  13. let copy = Object.assign({},obj);
  14. //不会改变源对象
  15. copy.age = 22;
  16. //会改变源对象
  17. copy.language.english = "bad";
  18. console.log("copy===obj:",copy===obj);//false
  19. console.log("copy:",copy);
  20. console.log("obj:",obj);

对于数组来说,也可以使用Array.prototype.slice()方法和Array.prototype.concact()方法

</>复制代码

  1. let obj = [1,2,["a","b","c"]]
  2. let copy = obj.slice();
  3. // 不会改变源对象
  4. copy[0]=111;
  5. // 会改变源对象
  6. copy[2][0]="aaa";
  7. console.log("copy===obj:",copy===obj);//false
  8. console.log("copy:",copy);
  9. console.log("obj:",obj);

深拷贝及实现方法

简单来说,深拷贝就是把对象以及对象的子对象进行拷贝。因为浅拷贝只复制了一层对象的属性,而如果对象的数值也为引用类型,那么拷贝过来依然是个引用地址,在拷贝对象上对子对象的值进行操作会改变原始数据中的值。

例如上面obj浅拷贝得到copy对象,如果在copy对象上改变子对象copy.language上的属性值,会影响到源对象obj

</>复制代码

  1. copy.language.english = "bad";
  2. copy.language.mandarin = "bad";
  3. console.log(obj.language); // {"english": "bad","mandarin": "bad",}

那么如何深拷贝呢?思路就是递归调用刚刚的浅拷贝,遍历所有值为引用类型的属性,然后依次赋值给另外一个对象即可。

方法一,自定义实现:

/**将源对象source深度合并到目标对象target上

source 源对象

target 目标对象,默认{}

deep 是否深度合并,默认true

*/

</>复制代码

  1. function deepCopy (source,target={},deep=true) {
  2. for (let key in source){
  3. // 深拷贝,而且只拷贝自身属性.(默认传入的source为对象)
  4. if (deep && source.hasOwnProperty(key)){
  5. if (typeof(source[key])==="object"){
  6. // // source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
  7. // if (!(target[key] instanceof Object) && (source[key] instanceof Object)){
  8. // target[key] = {};
  9. // }
  10. // // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
  11. // if (!Array.isArray(target[key]) && Array.isArray(source[key])) {
  12. // target[key] = [];
  13. // }
  14. // 上面的代码可以简化为以下:
  15. target[key] = Array.isArray(source[key]) ? [] : {};
  16. // 递归执行拷贝
  17. deepCopy(source[key],target[key],true);
  18. } else {
  19. target[key] = source[key];
  20. }
  21. }
  22. }
  23. return target;
  24. }
  25. let obj = {
  26. name: "slevin",
  27. age: 18,
  28. language: {
  29. english: "good",
  30. mandarin: "wonderful",
  31. test:[1,2,3]
  32. }
  33. }
  34. let copy = deepCopy(obj);
  35. copy.language.test[0] = 111;
  36. console.log("copy:",copy); //111 改变目标对象的子对象属性值
  37. console.log("obj:",obj); //1 对应源对象上没有改变

利用JSON.parse(JSON.stringify(copyObj))方法

</>复制代码

  1. let obj = {
  2. name: "slevin",
  3. age: 18,
  4. language: {
  5. english: "good",
  6. mandarin: "wonderful",
  7. test: [1, 2, 3]
  8. }
  9. }
  10. let copy = JSON.parse(JSON.stringify(obj))
  11. copy.language.test[0]=111;
  12. console.log("copy===obj:",copy===obj);//false
  13. console.log("copy.language.test[0]:",copy.language.test[0]);//111
  14. console.log("obj.language.test[0]:",obj.language.test[0]);//1

但要注意,此方法有两个缺点

如果源对象属性值有函数,无法拷贝下来

无法拷贝源对象原型链上的属性和方法

</>复制代码

  1. let obj = {
  2. name: "slevin",
  3. age: 18,
  4. language: {
  5. english: "good",
  6. mandarin: "wonderful",
  7. test: [1, 2, 3]
  8. },
  9. fn:function(){
  10. console.log("this:",this.name);
  11. }
  12. }
  13. let copy = JSON.parse(JSON.stringify(obj));
  14. console.log("copy===obj:",copy===obj);//false
  15. console.log("obj.fn:",obj.fn); //fn(){}...
  16. console.log("copy.fn:",copy.fn); //undefined

最后,再补一张引用类型的赋值操作、浅拷贝深拷贝对比图,加深印象

参考资料

js 深拷贝 vs 浅拷贝

深拷贝与浅拷贝的区别,实现深拷贝的几种方法

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101561.html

相关文章

  • JavaScript·随记 深拷贝 vs. 拷贝

    摘要:而在这个运算符的相关用例中,往往会涉及到其他知识点,深拷贝和浅拷贝就是其中之一。即对象的浅拷贝会对主对象的值进行拷贝,而该值有可能是一个指针,指向内存中的同一个对象。,可以看到深拷贝和浅拷贝是对复制引用类型变量而言的。 在ES6的系列文章中,基本都会提到Spread——扩展运算符(...)。而在这个运算符的相关用例中,往往会涉及到其他知识点,深拷贝和浅拷贝就是其中之一。 背景知识 在讨...

    RyanQ 评论0 收藏0
  • javascript对象的拷贝、深拷贝和Object.assign方法

    摘要:对象的浅拷贝浅拷贝是对象共用一个内存地址,对象的变化相互影响。这是特别值得注意的地方。和能正确处理的对象只有等能够被表示的数据结构,因此函数这种不能被表示的类型将不能被正确处理。 对象的浅拷贝: 浅拷贝是对象共用一个内存地址,对象的变化相互影响。比如常见的赋值引用就是浅拷贝: let srcObj = {name: lilei, age: 20}; let copyObj = srcO...

    lixiang 评论0 收藏0
  • JavaScript的深拷贝拷贝

    摘要:针对于的对象和数组数组也是对象浅拷贝只是引用,内存不变而深拷贝就是递归赋值。而浅拷贝会影响还可以用一句简单的代码实现上面的深拷贝 针对于JavaScript的对象和数组(数组也是对象)浅拷贝只是引用,内存不变;而深拷贝就是递归赋值。 深拷贝是不同内存,相互独立。而浅拷贝会影响 var arr = [1,2,3],arr2=[]; for(var i=0;i

    GHOST_349178 评论0 收藏0
  • JS 中的深拷贝拷贝

    摘要:什么是深拷贝浅拷贝见名知义,无论是深拷贝还是浅拷贝,都是的问题。使用如下以上就是关于中的深拷贝与浅拷贝的知识和如何进行深拷贝的知识了,如果有错或者有其他方式的话,欢迎在下面留言评论啦 前言 最近在写项目的时候涉及到一些父子组件传递个对象或者数组通信啥的,或者是直接复制添加对象啥的,直接使用赋值的时候总会出错。一查原来是浅拷贝的问题,就从网上找了点资料,汇总到这里来了。 1 什么是深拷贝...

    ztyzz 评论0 收藏0
  • 低门槛彻底理解JavaScript的深拷贝拷贝

    摘要:案例中的赋值就是典型的浅拷贝,并且深拷贝与浅拷贝的概念只存在于引用类型。修改修改经测试,也只能实现一维对象的深拷贝。经过验证,我们发现提供的自有方法并不能彻底解决的深拷贝问题。 在说深拷贝与浅拷贝前,我们先看两个简单的案例: //案例1 var num1 = 1, num2 = num1; console.log(num1) //1 console.log(num2) //1 num...

    wind3110991 评论0 收藏0

发表评论

0条评论

Nekron

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<