【前端面试题】深拷贝的终极实现

news/2024/10/21 9:09:10/

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

引子

通过本文可以学习到深拷贝的三种写法的实现思路与性能差异

首先,我们要理解什么是深拷贝,以及为什么要实现深拷贝

深拷贝是什么

通俗来讲,深拷贝就是深层的拷贝一个变量值。

为什么要实现深拷贝

因为在拷贝引用值时,由于复制一个变量只是将其指向要复制变量的引用内存地址,他们并没有完全的断开,而使用就可以实现深拷贝将其完全拷贝为两个单独的存在,指向不同的内存地址。

如何实现深拷贝

一行实现

let deepClone = JSON.parse(JSON.stringify(obj))
复制代码

这种是最简单的实现方法,但缺点是无法拷贝 Date()或是RegExp()。  

简单实现

function deepClone(obj) {// 判断是否是对象if (typeof obj !== 'object') return obj// 判断是否是数组 如果是数组就返回一个新数组 否则返回一个新对象var newObj = obj instanceof Array ? [] : {};// 遍历objfor (var key in obj) {// 将key值拷贝,再层层递进拷贝对象的值newObj[key] = deepClone(obj[key]);}// 返回最终拷贝完的值return newObj;
}
复制代码

对于普通的值(如数值、字符串、布尔值)和常见的引用类型(如对象和数组),这个写法完全够用。

但因为少了对 Date()  和  RegExp() 这些引用类型的特殊处理,这个写法一样不够完备。

普通版

function deepClone(origin, target) {// 判断target是否传入,如果未传入则创建一个{}let tar = target || {}; // 遍历origin对象for (var key in origin) {// 判断是否origin的自有属性if (origin.hasOwnProperty(key)) {  // 如果值是对象并且不为null,递归调用if (typeof origin[key] === 'object' && origin[key] !== null) {  // 根据值是否为数组创建初始化对象或数组tar[key] = Array.isArray(origin[key]) ? [] : {};// 递归调用 deepClone(origin[key], tar[key]);} else {  // 如果是简单类型,直接复制值tar[key] = origin[key];}}}// 返回最终拷贝完的值return tar;
}复制代码

这个深拷贝方法通过判断属性的值类型,实现了对 对象数组 以及 DateRegExp 等引用类型对象的递归拷贝,同时也考虑了拷贝基本类型值的情况,能够满足大多数场景的要求。

最终版

为什么还有最终版?

上面的案例,可以应对一般场景。

但是对于有两个对象相互拷贝的场景,会导致循环的无限递归,造成死循环!

Uncaught RangeError: Maximum call stack size exceeded

场景:

如何解决无限递归的问题?

首先我们要了解 WeakMap: WeakMap 的键名所引用的对象都是弱引用,不会被垃圾回收机制考虑,所以当对象只被WeakMap引用时,其所占用的内存会被垃圾回收。

而通过 WeakMap 记录已经拷贝过的对象,能防止循环引用导致的无限递归。

代码

实现简述:利用 WeakMap() 在属性遍历完绑定,并在每次循环时获取当前键名,如果存在则返回数据,不存在则拷贝。

function deepClone(origin, hashMap = new WeakMap()) {// 判断是否是对象if (origin == undefined || typeof origin !== 'object') return origin;// 判断是否是Date/RegExp类型if (origin instanceof Date) return new Date(origin);if (origin instanceof RegExp) return new RegExp(origin);// 判断是否已经克隆过此对象, 如果是直接返回const hashKey = hashMap.get(origin);if (hashKey) return hashKey;// *利用原型构造器获取新的对象 如: [], {}const target = new origin.constructor();// 将对象存入maphashMap.set(origin, target);// 循环遍历当前层数据for (let k in origin) {// 判断当前属性是否为引用类型if (origin.hasOwnProperty(k)) {target[k] = deepClone(origin[k], hashMap);}}return target;
}
复制代码

我们再来看一下使用最新版后的两个对象互相拷贝:

可以看到,通过使用 WeakMap 记录已经拷贝的对象,有效防止循环引用导致的栈溢出错误,是功能最完备的深拷贝实现。

总结

深拷贝可以完全拷贝一个对象,生成两个独立的且相互不影响的对象。

明白各种深拷贝实现的思路和性能差异,可以在不同场景选用最优的方案。

 大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全


http://www.ppmy.cn/news/63952.html

相关文章

RK平台移植rtl8852bs wifi驱动

RK平台 android 12的内核里面没有rtl8852bs wifi驱动,找模组原厂要了驱动,看了一些是其他平台的。。。要放RK平台是编译不过的,要做一下相应的修改,巨坑! 首先,修改kernel-5.10/drivers/net/wireless/rockchip_wlan/Kconfig: --- a/kernel-5.10/drivers/net/wireless/r…

[光源频闪] Basler相机光源频闪设置操作说明

📢博客主页:https://loewen.blog.csdn.net📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉📢现…

Java之多线程初阶2

目录 一.上节内容复习 1.进程和线程的区别 2.创建线程的四种方式 二.多线程的优点的代码展示 1.多线程的优点 2.代码实现 三.Thread类常用的方法 1.Thread类中的构造方法 2.Thread类中的属性 1.为线程命名并获取线程的名字 2.演示isDaemon() 3.演示isAlive() 4.演示…

BM61-矩阵最长递增路径

题目 给定一个 n 行 m 列矩阵 matrix ,矩阵内所有数均为非负整数。 你需要在矩阵中找到一条最长路径,使这条路径上的元素是递增的。并输出这条最长路径的长度。 这个路径必须满足以下条件: 对于每个单元格,你可以往上&#xff…

LeetCode周赛复盘(第344场周赛)

文章目录 1、找出不同元素数目差数组1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、频率跟踪器2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、6418. 有相同颜色的相邻元素数目3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、使二叉树所有路径值相等的最…

Java使用HTTP隧道

Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发,并在 1995 年正式推出。 后来 Sun 公司被 Oracle (甲骨文)公司收购,Java 也随之成为 O…

数据预处理简单介绍,并给出具体的代码示例

深度学习数据预处理简单介绍 深度学习的数据预处理包括以下几个方面: 数据读取和加载:将数据从存储介质中读取到内存中,用于后续的处理和训练。数据清洗和去噪:对数据进行处理,修复缺失值和错误,去除噪声…

C++好难(3):类和对象(中篇)

【本章目标】 类的6个默认成员函数构造函数析构函数拷贝构造函数赋值运算符重载const成员函数取地址及const取地址操作符重载 目录 【本章目标】 1.类的6个默认成员函数 2.构造函数 2.1概念 2.2构造函数的特性 特性一 特性二 特性三 特性四 特性五 特性六 特性七 …