【前端小tip】深拷贝不能处理函数的解决方法,文末包含所有深拷贝常见问题的解决方法

news2025/1/27 13:07:56

在开发过程中,我对对象进行深拷贝的时候常常使用序列化和反序列化,也就是

const newObj = JSON.parse(JSON.stringify(obj))

这个方法很好用,但是在最近我发现了一个弊端,就是它只能处理只含有基础类型属性和对象属性的对象,对函数没办法处理,例如以下对象

const obj = {
  name: "obj",
  friend: {
    name: "saber"
  },
  foo() {
    console.log("foo~")
  }
}

在用序列化和反序列化进行深拷贝后会输出:

{
  name: 'obj',
  friend: { name: 'saber' },
  foo: [Function: foo]
}	

很明显可以看出对函数的处理不是我们想要的结果,于是我们需要考虑用递归➕对象特判的方法实现

function deepClone(target) { 
    //检测数据类型
    if (typeof target !== 'object' || target === null) {
        // target 是 null ,或者不是对象和数组,说明是原始类型,直接返回
        return target;
    } else { 
        //创建一个容器,存储数组或者对象
        const result = Array.isArray(target) ? [] : {};
        //遍历target
        for(let key in target) { 
            //检测该属性是否为对象本身的属性(不能拷贝原型对象的属性)
            if(target.hasOwnProperty(key)) { 
            	//递归遍历子元素,直到能返回原始值
                result[key] = deepClone2(target[key]);
            }
        }
        return result;
    }
}

这样就可以成功处理函数了,输出如下:

{
  name: 'obj',
  friend: {
    name: 'saber'
  },
  foo: {}
}

在网上冲浪看看其他大神的解决办法,发现其他大神还发现了诸如循环引用引起无限递归、日期和正则表达式函数无法处理、Map和Set不好处理等问题,这里引用一个大神的方法,他几乎解决了所有常见问题,我也放进文章作为笔记:

   function deepClone(target) {
        // 创建一个 WeakMap 来保存已经拷贝过的对象,以防止循环引用
        const map = new Map();

        // 辅助函数:判断一个值是否为对象或函数
        function isObject(target) {
          return (
            (typeof target === "object" && target) || // 检查是否是非null的对象
            typeof target === "function" // 或者是函数
          );
        }

        // 主要的拷贝函数
        function clone(data) {
          // 基本类型直接返回
          if (!isObject(data)) {
            return data;
          }

          // 对于日期和正则对象,直接使用它们的构造函数创建新的实例
          if ([Date, RegExp].includes(data.constructor)) {
            return new data.constructor(data);
          }

          // 对于函数,创建一个新函数并返回
          if (typeof data === "function") {
            return new Function("return " + data.toString())();
          }

          // 检查该对象是否已被拷贝过
          const exist = map.get(data);
          if (exist) {
            return exist; // 如果已经拷贝过,直接返回之前的拷贝结果
          }

          // 如果数据是 Map 类型
          if (data instanceof Map) {
            const result = new Map();
            map.set(data, result); // 记录当前对象到 map
            data.forEach((val, key) => {
              // 对 Map 的每一个值进行深拷贝
              result.set(key, clone(val));
            });
            return result; // 返回新的 Map
          }

          // 如果数据是 Set 类型
          if (data instanceof Set) {
            const result = new Set();
            map.set(data, result); // 记录当前对象到 map
            data.forEach((val) => {
              // 对 Set 的每一个值进行深拷贝
              result.add(clone(val));
            });
            return result; // 返回新的 Set
          }

          // 获取对象的所有属性,包括 Symbol 类型和不可枚举的属性
          const keys = Reflect.ownKeys(data);
          // 获取对象所有属性的描述符
          const allDesc = Object.getOwnPropertyDescriptors(data);
          // 创建新的对象并继承原对象的原型链
          const result = Object.create(Object.getPrototypeOf(data), allDesc);

          map.set(data, result); // 记录当前对象到 map

          // 对象属性的深拷贝
          keys.forEach((key) => {
            result[key] = clone(data[key]);
          });

          return result; // 返回新的对象
        }

        return clone(target); // 开始深拷贝
      }

// 测试的sample对象
const sample = {
    // =========== 1.基础数据类型 ===========
    numberVal: 123,
    stringVal: "OpenAI",
    booleanVal: false,
    undef: undefined,
    nil: null,
    symKey: Symbol("key"),
    bigNumber: BigInt(1234567890n),
    // =========== 2.Object类型 ===========
    // 普通对象
    user: {
        firstName: "John",
        lastName: "Doe",
    },
    // 数组
    list: ["apple", "banana", "cherry"],
    // 函数
    display: function() {
        console.log("This is a display function");
    },
    // 日期
    birthDate: new Date(2000, 0, 1),
    // 正则
    pattern: new RegExp("/pattern/g"),
    // Map
    translations: new Map().set("hello", "hola"),
    // Set
    tags: new Set().add("fruit").add("food"),
    // =========== 3.其他 ===========
    [Symbol("unique")]: "uniqueValue",
};

// 4.添加不可枚举属性
Object.defineProperty(sample, "hidden", {
    enumerable: false,
    value: "Hidden Property",
});

// 5.设置原型对象
Object.setPrototypeOf(sample, {
    prototypeKey: "prototypeValue",
});

// 6.设置loop成循环引用的属性
sample.self = sample;

输出结果:
在这里插入图片描述

出处点这里
探究JavaScript中的深拷贝:细节与实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python专辑:大话数字类型

I will honour myself by showing up powerfully in my life today。我会为在今日努力生活的自我而感到自豪。 前面我们讲解过关于用Python写温度转换器,画“Python”以及天天学习的代码讲解。不知大家是否还记得,忘记的可以点击上面的专辑去里面复习一下…

反编译小程序详细教程,处理各种异常报错

文章目录 一、准备工作 (一)安装Nodejs (二)解密和逆向工具 二、小程序缓存文件解密 (一)定位小程序缓存路径 (二)源码解密 (三)源码反编译 三、小结 四、异常…

PTA作业笔记——简单的计算

PTA作业笔记——简单的计算 7-10 整数算术运算7-11 猫是液体7-11 猫是液体7-13 计算4个整数的平均值7-14 公元前后日期格式化7-15 A除以B7-18 出租车计价 7-10 整数算术运算 本题要求编写程序,计算并输出2个正整数的和、差、积、商与余数。题目保证输入和输出全部在…

数据集笔记 geolife (操作篇)

数据集介绍可看:数据集笔记:GeoLife GPS 数据 (user guide)_UQI-LIUWJ的博客-CSDN博客 1 读取数据 import os os.chdir(D:/Geolife Trajectories 1.3/Geolife Trajectories 1.3/Data/000/Trajectory)import pandas as pd data pd.read_csv(…

深度学习(Python)学习笔记2

第二章 感知机 2.1 感知机是什么 感知机接收多个输入信号,输出一个信号。 感知机的信号会形成流,向前方输送信息。 感知机的信号只有“流/不流”(1/0)两种取值。 本学习笔记中,0对应“不传递信号”,1对应“传递信号”。 图中、是输入信号,是输出信号,、是权重。图…

WebSocket和Html通讯

一、使用到的相关链接 参考 opencv-websocket: 基于opencv-web的实现 https://github.com/zaphoyd/websocketpp 安装教程 Websocket安装使用 - 知乎 (zhihu.com) 二、编译教程(Ubuntu为例) 遇到相关问题可以参考:Websocket安装使用 - 知乎…

删除无效的括号

题目链接 删除无效的括号 题目描述 注意点 s 由小写英文字母以及括号 ‘(’ 和 ‘)’ 组成1 < s.length < 25返回所有可能的结果。答案可以按 任意顺序 返回 解答思路 首先计算删除无效的括号最少次数需要删除的左括号和右括号的数量&#xff0c;方法是&#xff1a;…

vue3中 | 使用Pinia 进行状态管理 | pinia优化重复请求

问题&#xff1a;当多次调用同一个接口实现渲染时&#xff0c;会重复发送请求。 解决&#xff1a;使用 Pinia 进行数据集中管理&#xff0c;再把数据下发给组件&#xff0c;以供使用。 &#xff08;Pinia可以跨组件/页面共享状态。&#xff09; 1. 用 Pinia 管理数据&#xf…

Java时间复杂度和空间复杂度(详解)

目录 1.复杂度分析 2.时间复杂度 大O的渐进表示法 3.空间复杂度 1.复杂度分析 当我们设计一个算法时&#xff0c;怎样衡量其好坏&#xff1f; 算法在编写为可执行程序后&#xff0c;运行时需要耗费时间资源和空间&#xff08;内存&#xff09;资源。因此&#xff0c;衡量一…

【深度学习】 Python 和 NumPy 系列教程(五):Python容器:3、集合Set详解(初始化、访问元素、常用操作、常用函数)

目录 一、前言 二、实验环境 三、Python容器&#xff08;Containers&#xff09; 0、容器介绍 1、列表&#xff08;List&#xff09; 2、元组&#xff08;Tuple&#xff09; 3、集合&#xff08;Set&#xff09; 1. 初始化 2. 访问集合元素 3. 常用操作 a. 添加单个…

文件上传漏洞第十六关十七关

第十六关 第十七关 第十六关 直接上传php文件判断限制方式&#xff1a; 同第十五关白名单限制 第十六关源码&#xff1a; 代码逻辑判断了后缀名、content-type&#xff0c;以及利用imagecreatefromgif判断是否为gif图片&#xff0c;最后再做了一次二次渲染 二次渲染图片马&…

Linux虚拟机能ping通开发板的网络环境配置

Linux虚拟机能ping通开发板的网络环境配置 设备&#xff1a;Win10本地PC、ubuntu虚拟机、ARM linux开发板目标&#xff1a;三者可以互相ping通&#xff0c;即为搭建好了此网络环境预分配网段&#xff1a;192.168.1.1 - 192.168.1.255 本地PC:192.168.1.10 虚拟机&#xff1a;1…

关于GitHub Desktop中的“Open in Git Bash”无法使用的问题

问题描述 在GitHub Desktop中选择Repository--Open in Git Bash&#xff08;如图1&#xff09;&#xff0c;出现如图2所示结果。 图1 图2 解决办法&#xff08;Windows10&#xff09; 这个问题是由于Git的环境变量没有得到正确配置所导致的&#xff0c;所以需要正确设置环境变量…

Java线程之间通信方式

目录 1 线程之间的通信方式主要有以下几种2 共享变量3 锁机制4 条件变量5 信号量6 管道 1 线程之间的通信方式主要有以下几种 在实际开发时&#xff0c;一个进程中往往有很多个线程&#xff0c;大多数线程之间往往不是绝对独立的&#xff0c;比如说我们需要将A和B 两个线程的执…

C3d,C4d,C5d;

cl08267: ISOPREN_C2_like Superfamily

HBase 记录

HBase 管理命令 hbase hbck -details TABLE_NAME hbase hbck -repair TABLE_NAMEHBase概览 Master、RegionServer作用 RegionServer与Region关系 数据定位原理 https://blogs.apache.org/hbase/entry/hbase_who_needs_a_master RegionServer HBase Essentials.pdf (P25)…

解锁智慧照明新玩法,Construlita携手涂鸦智能打造创新方案!

近日&#xff0c;墨西哥头部照明品牌Construlita Lighting International SA DE CV&#xff08;以下简称&#xff1a;Construlita&#xff09;与全球化IoT开发者平台涂鸦智能&#xff08;NYSE: TUYA&#xff0c;HKEX: 2391&#xff09;在Construlita Connect发布会上宣布达成合…

Stable Diffusion 告别猜关键词,LoRA适配关键词自动生成

有没有想想过在SD绘图的时候下载好的LoRA模型选择之后不生效是为什么?或者说关键词不知道怎么填写? 这里介绍基于 Civitai 的LoRA 使用方法。 文章目录 Civitai 插件使用方法Civitai 插件 如果没有安装的小伙伴可以参考前面的文章先对 Civitai 的模型管理进行安装和使用,确…

龙芯指令集LoongArch——学习笔记(1)

1 龙芯架构 PDF下载链接&#xff1a; https://www.loongson.cn/download/index 1.1 龙芯架构概述 龙芯架构具有 RISC 指令架构的典型特征。 它的指令长度固定且编码格式规整&#xff0c; 绝大多数指令只有两个源操作数和一个目的操作数&#xff0c; 采用 load/store 架构&…

代码随想录算法训练营day42 | 动态规划 背包问题 01背包 二维数组一维数组 |416. 分割等和子集

动态规划&#xff1a;背包理论 背包理论基础 对于面试的话&#xff0c;其实掌握01背包&#xff0c;和完全背包&#xff0c;就够用了&#xff0c;最多可以再来一个多重背包。 如果这几种背包&#xff0c;分不清&#xff0c;我这里画了一个图&#xff0c;如下&#xff1a; 而完…