JS对象的深拷贝与浅拷贝

一、深拷贝与浅拷贝的概念

在进行JavaScript编程过程中,经常会涉及到对象的拷贝操作。对象的拷贝分为浅拷贝和深拷贝两种方式。

浅拷贝是指将一个对象复制到另一个对象,产生一个新的对象,新对象和原对象之间的基本类型数据相互独立,而对于引用类型的数据,依然指向原对象的存储空间。

深拷贝是指将源对象的值逐个复制到一个新的对象上,并且递归地将引用类型的数据也逐个复制到新对象上。这样,新对象和源对象就互相独立,互不影响。

二、浅拷贝的实现方式

浅拷贝可以通过Object.assign()或展开运算符(…)来实现。

//使用Object.assign()实现浅拷贝
let obj1 = {a: 1, b: 2};
let obj2 = Object.assign({}, obj1);
console.log(obj2); //{a: 1, b: 2}

//使用展开运算符实现浅拷贝
let obj1 = {a: 1, b: 2};
let obj2 = {...obj1};
console.log(obj2); //{a: 1, b: 2}

值得注意的是,浅拷贝只会复制原对象的第一层数据,如果原对象的某个属性是引用类型,那么新对象将使用同一个引用类型数据。

let obj1 = {a: 1, b: [1,2,3]};
let obj2 = {...obj1};
obj2.b.push(4);
console.log(obj1); //{a: 1, b: [1,2,3,4]}
console.log(obj2); //{a: 1, b: [1,2,3,4]}

三、深拷贝的实现方式

实现深拷贝的方式有很多,常用的有递归拷贝、JSON.parse()和JSON.stringify()等。

1.递归拷贝

递归拷贝通过递归遍历原对象,将原对象的每个子元素(属性)都逐个复制到新对象上,并且对于原对象的每个子元素如果是引用类型数据,就递归拷贝这个引用类型数据。

//递归拷贝实现函数
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepClone(obj[key]);
    }
  }
  return cloneObj;
}

//测试递归拷贝函数
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepClone(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

递归拷贝虽然实现简单,但是由于递归遍历可能会导致栈溢出,因此它的性能和可用性并不好。下面介绍另外两种方式。

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

将对象转换为JSON字符串,然后再将JSON字符串转换为对象的方式可以实现深拷贝,JSON.parse()和JSON.stringfy()就是这样一种方式。

let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

需要注意的是,通过JSON.stringify()和JSON.parse()方式实现深拷贝,存在一些限制:

  • 这种方式只能复制可枚举属性,而不能复制不可枚举属性和Symbol类型的属性。
  • 这种方式不能复制对象的方法。
  • 如果原对象的某个属性值为undefined、function、symbol,那么新对象中将不包含这些属性。
  • 如果原对象有循环引用的情况(即某个属性引用了对象本身),那么用JSON.stringify()将会抛出异常。

四、拷贝的性能问题

因为深拷贝要递归遍历整个对象,因此深拷贝的效率一般要比浅拷贝慢得多。在代码效率要求很高的情况下,应该优先选择使用浅拷贝而不使用深拷贝。深拷贝可以通过缓存已经处理过的对象来提高效率。

//使用Map缓存已经处理过的对象
function deepCloneWithMap(obj, map = new Map()) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  if (map.has(obj)) {
    return map.get(obj);
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  map.set(obj, cloneObj);
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepCloneWithMap(obj[key], map);
    }
  }
  return cloneObj;
}

//测试递归拷贝函数
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepCloneWithMap(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

代码示例

//浅拷贝示例
let obj1 = {a: 1, b: [1,2,3]};
let obj2 = {...obj1};
obj2.b.push(4);
console.log(obj1); //{a: 1, b: [1,2,3,4]}
console.log(obj2); //{a: 1, b: [1,2,3,4]}

//递归拷贝示例
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepClone(obj[key]);
    }
  }
  return cloneObj;
}
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepClone(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

//JSON.parse和JSON.stringify示例
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

//使用Map缓存已经处理过的对象的递归拷贝示例
function deepCloneWithMap(obj, map = new Map()) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  if (map.has(obj)) {
    return map.get(obj);
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  map.set(obj, cloneObj);
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepCloneWithMap(obj[key], map);
    }
  }
  return cloneObj;
}
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepCloneWithMap(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

原创文章,作者:GLNQZ,如若转载,请注明出处:https://www.506064.com/n/372284.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
GLNQZGLNQZ
上一篇 2025-04-24 06:40
下一篇 2025-04-24 06:40

相关推荐

  • JS Proxy(array)用法介绍

    JS Proxy(array)可以说是ES6中非常重要的一个特性,它可以代理一个数组,监听数据变化并进行拦截、处理。在实际开发中,使用Proxy(array)可以方便地实现数据的监…

    编程 2025-04-29
  • 面向对象编程、类和对象

    面向对象编程(Object-Oriented Programming, OOP)是一种编程方法,它将现实世界中的事物抽象为对象(Object),对象的属性和方法被封装成类(Clas…

    编程 2025-04-29
  • 解析js base64并转成unit

    本文将从多个方面详细介绍js中如何解析base64编码并转成unit格式。 一、base64编码解析 在JavaScript中解析base64编码可以使用atob()函数,它会将b…

    编程 2025-04-29
  • Node.js使用Body-Parser处理HTTP POST请求时,特殊字符无法返回的解决方法

    本文将解决Node.js使用Body-Parser处理HTTP POST请求时,特殊字符无法返回的问题。同时,给出一些相关示例代码,以帮助读者更好的理解并处理这个问题。 一、问题解…

    编程 2025-04-29
  • t3.js:一个全能的JavaScript动态文本替换工具

    t3.js是一个非常流行的JavaScript动态文本替换工具,它是一个轻量级库,能够很容易地实现文本内容的递增、递减、替换、切换以及其他各种操作。在本文中,我们将从多个方面探讨t…

    编程 2025-04-28
  • Mapster:一个高性能的对象映射库

    本文将深入介绍furion.extras.objectmapper.mapster,一个高性能的对象映射库,解释它是如何工作的以及如何在你的项目中使用它。 一、轻松地实现对象之间的…

    编程 2025-04-28
  • Python返回对象类型

    Python是一种动态、解释型、高级编程语言。Python是一种面向对象的语言,即所有的一切都是一个对象。 一、基本类型 Python中的基本类型有整数int、浮点数float、布…

    编程 2025-04-28
  • JS图片沿着SVG路径移动实现方法

    本文将为大家详细介绍如何使用JS实现图片沿着SVG路径移动的效果,包括路径制作、路径效果、以及实现代码等内容。 一、路径制作 路径的制作,我们需要使用到SVG,SVG是可缩放矢量图…

    编程 2025-04-27
  • Python中通过对象不能调用类方法和静态方法的解析

    当我们在使用Python编写程序时,可能会遇到通过对象调用类方法和静态方法失败的问题,那么这是为什么呢?接下来,我们将从多个方面对这个问题进行详细解析。 一、类方法和静态方法的定义…

    编程 2025-04-27
  • 如何使用JS调用Python脚本

    本文将详细介绍通过JS调用Python脚本的方法,包括使用Node.js、Python shell、child_process等三种方法,以及在Web应用中的应用。 一、使用Nod…

    编程 2025-04-27

发表回复

登录后才能评论