js 深浅拷贝

在JavaScript中,对象的拷贝是一项常见的操作。浅拷贝和深拷贝是两种常用的拷贝方式。浅拷贝只复制对象的引用,而深拷贝创建了一个全新的对象,包含与原始对象相同的值和结构。深拷贝和浅拷贝各有适用的场景和注意事项。本文将详细介绍如何实现一个完整而优雅的深拷贝函数,处理循环引用和特殊类型,优化性能,并探讨深拷贝和浅拷贝的应用场景、注意事项和相关属性。

首先我们来认识一下浅拷贝,什么是浅拷贝呢?

浅拷贝是指将一个对象的属性值复制到另一个对象,如果属性值是基本数据类型(如数字、字符串、布尔等),则直接复制该值;如果属性值是引用数据类型(如对象、数组等),则复制的是其引用地址,而不是实际的对象本身。

浅拷贝的实现方式有哪些?

1、Object.assign()

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
//  { a: 1, b: 4, c: 5 }

console.log(returnedTarget === target);
// true

 语法:

Object.assign(target, ...sources)

 参数:

target:需要应用源对象属性的目标对象,修改后将作为返回值。

sources:一个或多个包含要应用的属性的源对象。

如果目标对象与源对象具有相同的键(属性名),则目标对象中的属性将被源对象中的属性覆盖,后面的源对象的属性将类似地覆盖前面的源对象的同名属性。

Object.assign() 方法只会拷贝源对象可枚举的自有属性到目标对象。该方法在源对象上使用 [[Get]],在目标对象上使用 [[Set]],因此它会调用 getter 和 setter。故它对属性进行赋值,而不仅仅是复制或定义新的属性。如果合并源对象包含 getter 的新属性到原型中,则可能不适合使用此方法。

2、扩展运算符(…)

let source = { name: '张三', age: 30,sex:'女' };
let shallowCopy = { ...source };

 扩展运算符是三个点(…),它可以将一个数组或对象展开成多个元素,或将多个元素合并成一个组或数对象。

扩展运算符的作用

扩展运算符可以用于以下场景:

数组的展、合并、复制和解构赋值

对象的展开、合并、复制和解构赋值

函数参数的传递和返回值的处理

扩展运算符的使用限制
扩展运算符只能用于可迭代对象(如数组、字符串、Set、Map等),不能用于普通对象。

扩展运算符的性能问题
性能问题主要是由于扩展运算符在使用时会创建新的数组或对象,导致内存占用增加和对象复制的开销。特别是在大型数据集上使用扩展运算符时,会占用较多的内存并导致性能下降。另外,使用扩展运算符进行浅拷贝时,对于嵌套的对象或数组,仅会复制引用而不是真正的拷贝。这可能导致某些意外的副作用,因为原始对象的修改会影响到被复制的对象。

3、Array.prototype.concat()(用于数组的浅拷贝)

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);

console.log(array3);
// Array ["a", "b", "c", "d", "e", "f"]

 语法:

concat()
concat(value0)
concat(value0, value1)
concat(value0, value1, /* … ,*/ valueN)

 参数:数组和/或值,将被合并到一个新的数组中。如果省略了所有 valueN 参数,则 concat 会返回调用此方法的现存数组的一个浅拷贝

concat 方法创建一个新数组。该数组将首先由调用它的对象中的元素填充。然后,对于每个参数,它的值将被连接到数组中——对于普通对象或基元,参数本身将成为最终数组的一个元素;对于属性Symbol.isConcatSpreadable设置为真的数组或类数组对象,参数的每个元素都将是独立地添加到最终数组中。concat 方法不会递归到嵌套数组参数中。

concat() 方法是一种复制方法。它不会更改 this 或作为参数提供的任何数组,而是返回包含与原始数组中的元素相同的元素的浅拷贝

如果任何源数组是稀疏数组concat() 方法会保留空槽。

concat() 方法是通用的this 值的处理方式与其他参数相同(除了它会先转换为对象),这意味着普通对象将直接添加到结果数组中,而 @@isConcatSpreadable 属性为真值的类数组对象将展开并添加到数组中。

这几个方法比较常用。

后面呢在讲一下深拷贝。什么是深拷贝呢?

深拷贝是指将一个对象从堆内存中完全复制一份到栈内存中,新对象与原对象是完全独立的,修改新对象不会影响原对象。

深拷贝的实现方法有哪些?

1、递归实现

function deepClone(source) {
  if (typeof source !== 'object' || source == null) {
    return source;
  }
  const target = Array.isArray(source) ? [] : {};
  for (const key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (typeof source[key] === 'object' && source[key] !== null) {
        target[key] = deepClone(source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}

2、JSON.stringify()和JSON.parse() 

let a = {a:1,b:2}
let b = JSON.parse(JSON.stringify(a))
a.a = 11

a  // {a:11,b:2}
b // {a:1,b:2}

 但是他有缺陷

let a = {
    name: 'Jack',
    age: 18,
    hobbit: ['sing', {type: 'sports', value: 'run'}],
    score: {
        math: 'A',
    },
    run: function() {},
    walk: undefined,
    fly: NaN,
    cy: null,
    date: new Date()
}
let b = JSON.parse(JSON.stringify(a))

 

 取不到值为 undefined 的 key;如果对象里有函数,函数无法被拷贝下来;无法拷贝copyObj对象原型链上的属性和方法;对象转变为 date 字符串。

然后,在实际项目中,我们深浅拷贝,一般使用lodash库。

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清爽豆干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值