【JavaScript】深拷贝与浅拷贝

  • 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用。
  • 深拷贝拷贝多层, 每一级别的数据都会拷贝。
  • 深浅拷贝的不同是针对引用数据类型而言的。

由于引用数据的变量值只是一个指向该引用数据的指针(地址)。所以对于引用数据来说,浅拷贝只复制了这个指针(地址)值,而没有复制对象本身,新旧对象还是共享同一块内存,因此同新对象改变属性值会对原对象造成影响。但深拷贝会另外创造一个一模一样的对象,把每一层的数据都拷贝过去,新对象跟原对象不共享内存,修改新对象不会改到原对象。

引用类型一般是数组和对象,会出现浅拷贝问题,因此分为两个部分。

一、对象浅拷贝的解决方案

1. JSON转换方法

  • JSON.stringify(obj) 将一个 JavaScript 对象或值转换为 JSON 字符串,称之为 JSON 序列化
  • JSON.parse(str) 解析JSON字符串,构造由字符串描述的JavaScript值或对象,称之为 JSON 反序列化

首先将对象转为JSON字符串,这样它里面的所有内容都包含在这个字符串中,变成了一个普通数据类型,再将这个字符串转回对象类型,赋值给新的变量,这时候是一个根据字符串解析得到的新对象,与原对象不一样,也就是变量中存的是一个新的地址,完成深拷贝。

const obj1 = {
  a: 1,
  b: undefined,
  arr: [1, 2, 3],
  fun: () => {}
}
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);

在这里插入图片描述

【缺点】数据类型为function和数据值为undefined的属性不会被拷贝过来。

【应用场景】当对象中没有function和undefined数据时,这种方法比较简洁也能达到深拷贝的效果。

2. Object.assign()

ES6 新增的浅拷贝方法:Object.assign(target, ...sources),用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。如果出现重复属性,则后面的属性值覆盖前面的属性值。

const obj1 = {
  a: 1,
  b: undefined,
  arr: [1, 2, 3],
  fun: () => {}
}
let obj2 = Object.assign({}, obj1);
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);

在这里插入图片描述
【特点】所有数据类型均可拷贝,但是只拷贝了一级属性,如果属性中存在引用数据类型,还是无法完全拷贝。

【应用场景】当对象中数据编辑简单,没有二级属性时,可以使用该方法拷贝。

3. 扩展运算符

ES6中对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

const obj1 = {
  a: 1,
  b: undefined,
  arr: [1, 2, 3],
  fun: () => {}
}
let obj2 = {...obj1};
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);

在这里插入图片描述
特点和应用场景与上一种方法一样。

4. 递归

function deepClone(data) {
  //首先判断要拷贝的值是什么类型,数组or对象
  const newdata = Array.isArray(data) ? [] : {};
  // 如果data是对象,k表示属性名
  // 如果data是数组,k表示索引,数组可以看成是一个对象,属性值就是索引值
  for (let k in data) {
    // 数组和对象的typeof值都是object
    // 如果data[k]的typeof值是object,说明这个属性值是引用类型数据,需要做深拷贝
    if (data[k] && typeof data[k] === 'object') {
      newdata[k] = deepClone(data[k]);
    } else {
      newdata[k] = data[k];
    }
  }
  return newdata;
}

const obj1 = {
  a: 1,
  b: undefined,
  arr: [1, 2, 3],
  fun: () => {}
}
let obj2 = deepClone(obj1);
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);

在这里插入图片描述
递归是一种比较完美的解决方案,可以完全实现深拷贝,但是实现方法复杂。所以一般先进行数据类型的判断,如果数据类型没有太复杂,可以优先选择上面三种方法。

二、数组浅拷贝的解决方案

数组浅拷贝和对象浅拷贝的解决方案基本类似。

1. JSON转换方法

与对象的解决方案类似,先将对象转为JSON字符串,再将这个字符串转回对象类型,赋值给新的变量,就可以实现深拷贝。

const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);

在这里插入图片描述
同样拷贝特点也一样,数据类型为function和数据值为undefined的属性不会被拷贝过来或者拷贝出错。

2. Object.assign()

const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = Object.assign([], arr1);
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);

在这里插入图片描述
【特点】所有数据类型均可拷贝,但是只拷贝了一级属性,如果属性中存在引用数据类型,还是无法完全拷贝。

3. 扩展运算符

实现效果与上一种方法完全相同。这里只给出代码,不展示效果了。

const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = [...arr1];
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);

4. slice(),concat()

数组的slice()方法以新的数组对象,返回数组中被选中的元素,且不会改变原数组。
数组的concat()方法方法用于连接两个或多个数组,不会更改原数组,而是返回一个新数组,其中包含已连接数组的值。
实现效果与上两种方法相同。这里只给出代码,不展示效果了。

const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = arr.slice();
// let arr2 = [];
// let arr2 = arr2.concat(arr1);
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);

5. 递归

比较完美的解决方案,可直接调用上面封装的deepClone函数。不再详细描述。

三、总结

引用数据类型的浅拷贝解决方案主要包括:

  1. JSON转换。先使用JSON.stringify(data)将数转换为JSON字符串,再使用JSON.parse(str)将字符串转回对象。这种方法可以实现深拷贝,但不会拷贝function和undefined数据。
  2. Object.assign(data)。所有类型的数据都可以拷贝,但是只能拷贝一级属性,不能拷贝二级及以上的属性。
  3. 扩展运算符。所有类型的数据都可以拷贝,但是只能拷贝一级属性,不能拷贝二级及以上的属性。
  4. slice(),concat()。数组专属。所有类型的数据都可以拷贝,但是只能拷贝一级属性,不能拷贝二级及以上的属性。
  5. 递归。较为完美的解决方案。但实现起来较为复杂。

在使用过程中,应当结合数据的情况选用具体的解决方案,如果数据中不存在undefined,function类型的属性值,优先选择1;如果数据只有一级属性,则优先选择2-4;如果数据类型很复杂,这时候考虑使用5。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值