javascript深拷贝和浅拷贝

567 阅读4分钟

同事分享的javascript深拷贝和浅拷贝,转发过来了~~~

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

1.深拷贝和浅拷贝概念

浅拷贝:浅拷贝指的就是循环遍历对象一遍,将该对象上的属性赋值到另一个对象上。在这个过程中属性值为基本类型则拷贝的就是基本类型的值;若该值为引用类型,则拷贝的就是就是一个内存地址。

深拷贝:深拷贝其实就是浅拷贝的进阶版,因为浅拷贝只循环遍历了一层数据,对于引用类型拷贝的是对象的地址,但是深拷贝会进行多层的遍历,将所有数据进行数据层面的拷贝。下面就利用三种方式实现深拷贝。

const b1 = {
    m: 10,
    n: 20
};

const b2 = b1;
b1.m = 20;
console.log(b2); // { m: 20, n: 20 }

2.常见的浅拷贝

(1) Object.assign()方法

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。也就是说,如果对象的属性值为简单类型(如string, number),通过Object.assign({},srcObj);得到的新对象为深拷贝;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。

(2) ...扩展运算符

还可以通过展开运算符...来实现浅拷贝

let a = {age: 1}

let b = { ...a }

a.age = 2

console.log(b.age) // 1
(3) Array.prototype.concat

arrayObject.contact(array), 返回一个新的数组,包含旧数组和新数组所有元素。

(4) Array.prototype.slice

arrayObject.slice(start,end), 返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。

总结:如果一个对象的属性值都是基本数据类型,这些方法可以实现深拷贝效果,如果对象属性值包括引用类型,那么这些方法只能实现浅拷贝效果

3.常见的深拷贝

(1) 乞丐版

首先来看一下最简单的深拷贝方式,就是利用JSON.stringify()和JSON.parse(),但是该方式其实是存在很多问题的:

  • 不能正确处理正则表达式,其会变为空对象

  • 不能正确处理函数,其变为undefined

  • 不能正常输出值为undefined的内容

  • 会将时间对象转换成字符串;

function cloneDeep(source) {
    return JSON.parse(JSON.stringify(source));
}

const obj = {
    a: 10,
    b: undefined,
    c: /\w/g,
    d: function() {
        return true;
    },
    e: new Date()
};

console.log(obj); // { a: 10, b: undefined, c: /\w/g, d: [Function: d],e: 2021-08-30T01:29:41.180Z }

console.log(cloneDeep(obj)); // { a: 10, c: {}, e: '2021-08-30T01:29:41.180Z' }

(2) 递归版

function cloneDeep(source) {
 // 如果输入的为基本类型,直接返回
 if (typeof source !== 'object' || source === null) {
   return source;
 }
 if (source instanceof Date) return new Date(obj);
 if (source instanceof RegExp) return new RegExp(obj);
 // 判断输入的为数组还是对象,进行对应的创建
 const target = Array.isArray(source) ? [] : {};
 for (let [key, value] of Object.entries(source)) {
   target[key] = cloneDeep(value);
 }
 return target;
}
const obj = {
 a: 10,
 b: undefined,
 c: /\w/g,
 d: function() {
   return true;
 },
 e: {
   m: 20,
   n: 30
 }
};
const result = cloneDeep(obj);
result.e.m = 100;
console.log('拷贝前:', obj);
console.log('拷贝后:', result);

总结:

(1) 最好不要用for ... in方法遍历对象属性,该方法会遍历原型链上的属性

(2) 没有解决循环引用问题,存在爆栈问题

解决爆栈问题一种简单的方式就是把已添加的对象记录下来,这样下次碰到相同的对象引用时,直接指向记录中的对象即可。要实现这个记录功能,我们可以借助 ES6 推出的 WeakMap 对象,该对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

blog.csdn.net/fly_wugui/a…

(3) 一些JS内置对象,需要额外判断返回

4.项目案例分析

场景一

这是一个浅拷贝,当salesOrderAo某些属性值是引用类型时,修改它的值,也会同时修改对象salesOrderAoInfo相应的属性值, salesOrderAoInfo 为当前组件初始化对象,不应该修改它的值,此处应该使用深拷贝

const salesOrderAoInfo = {
  businessStatus: null, // 业务状态 1作废、2完结
  ids: [], // ids列表
  status: null, //3审核通过  5审核拒绝
  rejectReason: '', //审核拒绝原因(审核拒绝时必填)
}

salesOrderAo: Object.assign({}, salesOrderAoInfo),
场景二
query() {
  const [startDate, endDate] = this.listQuery.date ? this.listQuery.date : []
  const formData = { ...this.listQuery }
  delete formData.date
  delete formData.settlementName
  formData.startDate = startDate
  formData.endDate = endDate
  this.$emit('handleSearch', formData)
}