JS深拷贝详解

一、概述

在JS开发中,我们常常需要复制或者克隆一个对象。对于简单数据类型,如数字、字符串、布尔值等,这是很容易实现的。但是对于复杂数据类型,如对象或者数组,就需要用到JS的深拷贝。深拷贝是指将一个对象完全复制一份,并开辟一个新的内存空间存放新对象,新对象的改动不会影响原对象。相比之下,JS的浅拷贝只是复制对象的引用,并没有开辟新的内存空间,因此改动新对象也会影响原对象。

二、基本实现方法

JS的深拷贝可以通过两种方法实现。

1. JSON.parse()

function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

通过JSON.stringify()将对象转化为字符串,然后再通过JSON.parse()将字符串转化为新的对象,新对象就是原对象的深拷贝。这种方法的优点是实现简单,代码量小,但是缺点也显而易见:无法处理对象中含有函数、正则表达式等非JSON格式数据类型。而且该方法在处理大型对象的时候性能很低,因为需要进行两次序列化和反序列化操作。

2. 递归

function deepClone(obj, hash = new WeakMap()) {
  if (Object(obj) !== obj) return obj;
  if (hash.has(obj)) return hash.get(obj);
  let clonedObj = new obj.constructor();
  hash.set(obj, clonedObj);
  if (obj instanceof Map) {
    obj.forEach((value, key) => {
      clonedObj.set(deepClone(key, hash), deepClone(value, hash));
    });
  } else if (obj instanceof Set) {
    obj.forEach(value => {
      clonedObj.add(deepClone(value, hash));
    });
  } else {
    Object.getOwnPropertyNames(obj).forEach(prop => {
      if (Object.getOwnPropertyDescriptor(obj, prop).enumerable) {
        clonedObj[prop] = deepClone(obj[prop], hash);
      }
    });
  }
  return clonedObj;
}

递归方法则不会存在以上的问题,可以处理复杂对象,但是要注意对象中是否包含自引用的情况,因此需要引入哈希表记忆化处理。

三、对象中含有函数的深拷贝

使用递归方式进行深拷贝的时候,如果对象中含有函数,就会出现深拷贝失误的情况。因为递归过程中不会拷贝函数。因此需要单独处理对象中的函数,这可以分为两步:

1. 拷贝函数

let _toString = Object.prototype.toString;
let _hasOwnProperty = Object.prototype.hasOwnProperty;

function isFunction(obj) {
  return _toString.call(obj) === '[object Function]' || typeof obj === 'function';
}

function cloneFunction(func) {
  let bodyReg = /(?<={)(.|\n)+(?=})/m;
  let paramReg = /(? param.trim());
        return new Function(...params, body[0]);
      } else {
        return new Function(body[0]);
      }
    } else {
      return null;
    }
  } else {
    return eval(funcString);
  }
}

function copy(obj, hash = new WeakMap()) {
  if (Object(obj) !== obj) return obj;
  if (hash.has(obj)) return hash.get(obj);
  let type = _toString.call(obj);
  let copiedObj;
  switch (type) {
    case '[object Array]':
      copiedObj = new obj.constructor();
      break;
    case '[object Function]':
      return cloneFunction(obj);
    case '[object RegExp]':
      copiedObj = new RegExp(obj.source, obj.flags);
      break;
    case '[object Date]':
      copiedObj = new Date(obj.getTime());
      break;
    case '[object Set]':
      copiedObj = new Set();
      break;
    case '[object Map]':
      copiedObj = new Map();
      break;
    default:
      copiedObj = new obj.constructor();
  }
  hash.set(obj, copiedObj);
  if (obj instanceof Map) {
    obj.forEach((value, key) => {
      copiedObj.set(copy(key, hash), copy(value, hash));
    });
  } else if (obj instanceof Set) {
    obj.forEach(value => {
      copiedObj.add(copy(value, hash));
    });
  } else {
    for (let prop in obj) {
      if (_hasOwnProperty.call(obj, prop)) {
        if (type === '[object Array]' || type === '[object Object]') {
          copiedObj[prop] = copy(obj[prop], hash);
        } else {
          Object.defineProperty(copiedObj, prop, Object.getOwnPropertyDescriptor(obj, prop));
        }
      }
    }   
  }
  return copiedObj;
}

上述代码实现了函数的拷贝,其中cloneFunction()函数就是用于拷贝函数的。

2. 拷贝对象中的函数

function deepClone(obj, hash = new WeakMap()) {
  if (Object(obj) !== obj) return obj;
  if (hash.has(obj)) return hash.get(obj);
  let clonedObj = copy(obj, hash);
  hash.set(obj, clonedObj);
  return clonedObj; 
}

在进行递归遍历后,调用copy()方法对对象进行拷贝,并记录到哈希表中。这样即可实现深拷贝。

四、应用场景

JS的深拷贝主要用于解决对象的克隆和数据格式转化等问题。在以下场景中深拷贝会被广泛应用:

1. React组件之间的通信

在React组件内部,有时需要将一个组件的状态传递给子组件,这时候就需要使用深拷贝将状态复制一份,以防止子组件改变父组件的状态,从而避免数据污染的情况。

2. 前端缓存

在前端开发中,经常需要缓存一些数据到本地存储中。如果数据源是一个对象,使用深拷贝可以保证数据不会在缓存的过程中被修改,从而避免数据完整性的问题。

3. 数据结构的复制

在JS中,集合类数据结构(例如Map,Set等)是常用的存储数据的方式。在使用时,需要对这些数据结构进行复制,以便进行修改、删除等操作。使用深拷贝可以确保复制后的数据和原数据完全独立,防止由于互相引用导致结果不可预知。

4. 数据类型转化

在开发中,有时需要将一个数据格式转换成另一个格式。例如将一个JSON对象转换为XML或CSV格式,可以通过先将JSON对象深拷贝得到一个JS对象,然后再将JS对象转换为需要的格式。

五、总结

JS的深拷贝对于复制对象是一项非常关键的技术,掌握深拷贝的方法有助于提高代码效率和数据完整性。本文介绍了两种实现深拷贝的方法,以及如何处理对象中的函数,同时对深拷贝的应用场景作了介绍。

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

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
HUDVO的头像HUDVO
上一篇 2025-04-22 01:14
下一篇 2025-04-22 01:14

相关推荐

  • JS Proxy(array)用法介绍

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

    编程 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
  • JS图片沿着SVG路径移动实现方法

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

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

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

    编程 2025-04-27
  • 如何反混淆美团slider.js

    本文将从多个方面详细阐述如何反混淆美团slider.js。在开始之前,需要明确的是,混淆是一种保护JavaScript代码的方法,其目的是使代码难以理解和修改。因此,在进行反混淆操…

    编程 2025-04-27
  • Python要学JS吗?

    Python和JavaScript都是非常受欢迎的编程语言。然而,你可能会问,既然我已经学了Python,是不是也需要学一下JS呢?在本文中,我们将围绕这个问题进行讨论,并从多个角…

    编程 2025-04-27
  • 解决js ajax post 419问题

    对于使用ajax post请求时出现的419问题,我们需要进行以下几个方面的阐述,包括返回码的含义、可能出现的情况、解决方案等内容。 一、解析419返回码 419返回码表示用户超时…

    编程 2025-04-27
  • 神经网络代码详解

    神经网络作为一种人工智能技术,被广泛应用于语音识别、图像识别、自然语言处理等领域。而神经网络的模型编写,离不开代码。本文将从多个方面详细阐述神经网络模型编写的代码技术。 一、神经网…

    编程 2025-04-25

发表回复

登录后才能评论