深拷贝和浅拷贝的踩坑记录

什么是深拷贝和浅拷贝

基础回顾

JS中两大数据类型:
基本类型:Undefined、Null、Boolean、Number、String、Symbol
引用类型:Object Array
基本类型就是值类型, 存放在栈(stack)内存中的简单数据段,数据大小确定,内存空间大小可以分配
引用类型, 存放在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置

为什么要用深拷贝和浅拷贝

下面来看一个实例,分别对两种类型做一个copy处理

let obj = {
    name: 'date',
    value: '20210422'
}

let obj2 = obj 
let obj3 = obj.name  

console.log(obj2.value) //20210422
console.log(obj3) // date

// 改变obj2,obj3
obj2.value = '2021年4月22日'
obj3 = '日期'

console.log(obj.value) // 2021年4月22日
console.log(obj.name) // date
复制代码

如上所示,obj2和obj3分别是对一个对象的浅拷贝和深拷贝。改变obj2的值,obj的对应的值也会发生相应的改变,因为obj2是obj的浅拷贝,拷贝的是obj指针指向的地址,因此obj和obj2操作的是同一对象。 而obj和obj3是完全独立的,因为obj3拷贝的是obj的值,而非引用地址。

怎样实现深拷贝和浅拷贝

浅拷贝实现方式

  1. “=”赋值

  2. Object.assign()

Object.assign是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

Object.assign(target, ...sources)
复制代码

参数:
target:目标对象。
sources:任意多个源对象。
返回值:目标对象会被返回。

var obj1 = { a: {a: "hello", b: 'world'} };
var obj2 = Object.assign({}, obj1);

obj2.a.a = "hi";
console.log(obj1.a.a); // "hi"
复制代码
  1. 展开运算符…
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
复制代码
  1. Array.prototype.concat()
let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2 = arr.concat();    
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
复制代码

5.Array.prototype.slice()

let arr = [1, 3, {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
复制代码

深拷贝实现方式

  1. 手动赋值
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
复制代码
  1. JSON做字符串转换

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

var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false
复制代码

这样做是真正的Deep Copy,这种方法简单易用。

但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。

这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝

也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。

var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 没复制
复制代码
  1. 递归拷贝
function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : {};            
      arguments.callee(prop, obj[i]);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
复制代码

总结

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,两个对象指向同一个地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,两个对象指向不同的地址,因此修改新对象不会影响原对象。

踩坑点

在项目开发中点击编辑会有编辑弹窗跳出,可以对现有的信息进行编辑改变,defaultForm保存的是后端返回的编辑前的值,打开弹窗的可编辑表单双向绑定的是roleForm的值,因此一开始点击编辑弹出弹窗会触发的click事件中将defaultForm的值赋值给了roleForm(!!!!因为此处是浅拷贝,所以改变了roleForm的值后,未点击“确定”按钮,点击“取消”弹窗消失后回显的居然是编辑后的数据,这很明显是BUG!!!!没想到自己不注意的细节踩了这么一个坑,记录一下,学而时习之温故而知新)

参考链接

segmentfault.com/a/119000001…
segmentfault.com/a/119000001…
segmentfault.com/a/119000001…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享