JS - 浅拷贝和深拷贝

发布时间:2023年12月17日

1,概念

浅拷贝:只拷贝基本类型的数据,引用类型的数据拷贝后也会发生引用,因为拷贝的是对象的引用地址。

深拷贝:在堆中重新分配内存,并把源对象中所有属性都新建拷贝,新旧对象完全隔离,互不影响。

2,浅拷贝

1,直接赋值

const user = {
  name: '下雪天的夏风'
}

const user2 = user
user.name = 'xxx'

console.log(user); // { name: 'xxx' }
console.log(user2); // { name: 'xxx' }
console.log(user === user2); // true

2,Object.assign()

MDN 参考

const user = {
  name: "下雪天的夏风",
  address: {
    province: "陕西",
    city: "西安",
  },
};

const user2 = Object.assign({}, user);
user2.name = "xxx";
user2.address.province = "北京";

console.log(user); // { name: '下雪天的夏风', address: { province: '北京', city: '西安' } }
console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }

3,扩展运算符

const user = {
  name: "下雪天的夏风",
  address: {
    province: "陕西",
    city: "西安",
  },
};

const user2 = { ...user };
user2.name = "xxx";
user2.address.province = "北京";

console.log(user); // { name: '下雪天的夏风', address: { province: '北京', city: '西安' } }
console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }

4,数组 slice() 和 concat()

数组元素是引用类型时,同样拷贝的是引用地址。

const arr1 = [1, true, "Hello", { name: "下雪天的夏风", age: 18 }];
// slice() 替换为 concat() 结果一致。
const arr2 = arr1.slice();

arr2[0] = 2;
arr2[3].age = 36;

console.log(arr1); // [ 1, true, 'Hello', { name: '下雪天的夏风', age: 36 } ]
console.log(arr2); // [ 2, true, 'Hello', { name: '下雪天的夏风', age: 36 } ]

5,jquery 中的 $.extend()

jQuery 中,$.extend([deep], target, object1, [objectN]) 方法可进行深浅拷贝。jquery 中文文档

  • deep:true 为深拷贝,默认为 false 浅拷贝
  • target:目标对象
  • object1:待拷贝到第一个源对象
  • objectN:待拷贝到第N个源对象
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
  const user = {
    name: "下雪天的夏风",
    address: {
      province: "陕西",
      city: "西安",
    },
  };

  const user2 = {};
  $.extend(user2, user);
  
  user2.name = "xxx";
  user2.address.province = "北京";
  
  console.log(user); // { name: '下雪天的夏风', address: { province: '北京', city: '西安' } }
  console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }
</script>

6,lodash.clone()

loadsh 中文文档

<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
  const user = {
    name: "下雪天的夏风",
    address: {
      province: "陕西",
      city: "西安",
    },
  };

  const user2 = _.clone(user);
  
  user2.name = "xxx";
  user2.address.province = "北京";
  
  console.log(user); // { name: '下雪天的夏风', address: { province: '北京', city: '西安' } }
  console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }
</script>

3,深拷贝

遇到的问题和使用建议后面会介绍。

1,JSON.parse(JSON.stringify)

const user = {
  name: "下雪天的夏风",
  address: {
    province: "陕西",
    city: "西安",
  },
};

const user2 = JSON.parse(JSON.stringify(user));

user2.name = "xxx";
user2.address.province = "北京";

console.log(user); // { name: '下雪天的夏风', address: { province: '陕西', city: '西安' } }
console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }

2,structuredClone()

全局属性。浏览器环境是 window 的属性,node 环境是 global 的属性。

const user = {
  name: "下雪天的夏风",
  address: {
    province: "陕西",
    city: "西安",
  },
};


const user2 = structuredClone(user);

user2.name = "xxx";
user2.address.province = "北京";

console.log(user); // { name: '下雪天的夏风', address: { province: '陕西', city: '西安' } }
console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }

3,jquery 中的 $.extend()

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
  const user = {
    name: "下雪天的夏风",
    address: {
      province: "陕西",
      city: "西安",
    },
  };

  const user2 = {};
  $.extend(true, user2, user);
  
  user2.name = "xxx";
  user2.address.province = "北京";
  
  console.log(user); // { name: '下雪天的夏风', address: { province: '陕西', city: '西安' } }
  console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }
</script>

4,lodash.cloneDeep()

<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
  const user = {
    name: "下雪天的夏风",
    address: {
      province: "陕西",
      city: "西安",
    },
  };

  const user2 = _.cloneDeep(user);
  
  user2.name = "xxx";
  user2.address.province = "北京";
  
  console.log(user); // { name: '下雪天的夏风', address: { province: '陕西', city: '西安' } }
  console.log(user2); // { name: 'xxx', address: { province: '北京', city: '西安' } }
</script>

4,遇到的问题和使用建议

遇到的问题

可以看到,上面的例子中使用的都是简单的基本数据类型和普通对象,这也是日常开发中最常见的。

但还有一些特殊情况,比如函数在进行深拷贝时,其实和基本类型一样直接拷贝即可。但函数不支持序列化和结构化算法!所以 JSON.parse(JSON.stringify)structuredClone() 无法处理函数,所以源对象中有函数时会有问题!

const user = {
  getAge() {
    console.log(18);
  },
};

const user2 = JSON.parse(JSON.stringify(user));
console.log(user2); // {},函数直接被忽略
const user = {
  getAge() {
    console.log(18);
  },
};

// 结构化算法无法处理函数。
// Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': getAge() 
const user2 = window.structuredClone(user);

还有一些特殊的对象,比如日期对象,正则对象,包装类型,Map,Set 等,在拷贝时都需要特殊处理。

使用建议

在生成环境中的建议:

  • 如果只是简单的数据类型,比如基本类型和普通对象,用哪种方法都无所谓。
  • 如果有复杂的类型,建议使用 lodash.cloneDeep()$.extend()

5,手动递归简单实现深拷贝

特殊的类型就不考虑了,主要是完善下 JSON.parse(JSON.stringify) 不支持函数的拷贝。Map 和 Set 其实也是判断后遍历递归即可。

function deepClone(target) {
  let result;
  // 基本类型、函数,直接赋值。
  if (typeof target !== "object" || typeof target === null) {
    result = target;
  } else {
    result = Array.isArray(target) ? [] : {};
    for (const key in target) {
      result[key] = deepClone(target[key]);
    }
  }
  return result;
}

以上。

参考-如何实现深拷贝

文章来源:https://blog.csdn.net/qq_40147756/article/details/135005693
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。