你最少用几行代码实现深拷贝?

  发布时间:2025-11-05 15:49:26   作者:玩站小弟   我要评论
深度克隆(深拷贝)一直都是初、中级前端面试中经常被问到的题目,网上介绍的实现方式也都各有千秋,大体可以概括为三种方式:JSON.stringify+JSON.parse, 这个很好理解;全量判断类型, 。

深度克隆(深拷贝)一直都是最少初、中级前端面试中经常被问到的用行题目,网上介绍的代码实现方式也都各有千秋,大体可以概括为三种方式:

JSON.stringify+JSON.parse,实现深拷 这个很好理解;全量判断类型,根据类型做不同的最少处理;2的变型,简化类型判断过程。用行

前两种比较常见也比较基础,代码所以我们今天主要讨论的实现深拷是第三种。

问题分析

深拷贝自然是最少相对浅拷贝 而言的。我们都知道引用数据类型 变量存储的用行是数据的引用,就是代码一个指向内存空间的指针, 所以如果我们像赋值简单数据类型那样的实现深拷方式赋值的话,其实只能复制一个指针引用,最少并没有实现真正的用行数据克隆。

通过这个例子很容易就能理解:

const obj1 = {

name: superman

}

const obj2 = obj1;

obj1.name = 前端切图仔;

console.log(obj2.name); // 前端切图仔

所以深度克隆就是代码为了解决引用数据类型不能被通过赋值的方式 复制 的免费源码下载问题。

引用数据类型

我们不妨来罗列一下引用数据类型都有哪些:

ES6之前:对象, 数组, 日期, 正则表达式, 错误,ES6之后:Map, Set, WeakMap, WeakSet,

所以,我们要深度克隆,就需要对数据进行遍历并根据类型采取相应的克隆方式。当然因为数据会存在多层嵌套的情况,采用递归是不错的选择。

简单粗暴版本function deepClone(obj) {

let res = {};

// 类型判断的通用方法

function getType(obj) {

return Object.prototype.toString.call(obj).replaceAll(new RegExp(/[|]|object /g), "");

}

const type = getType(obj);

const reference = ["Set", "WeakSet", "Map", "WeakMap", "RegExp", "Date", "Error"];

if (type === "Object") {

for (const key in obj) {

if (Object.hasOwnProperty.call(obj, key)) {

res[key] = deepClone(obj[key]);

}

}

} else if (type === "Array") {

console.log(array obj, obj);

obj.forEach((e, i) => {

res[i] = deepClone(e);

});

}

else if (type === "Date") {

res = new Date(obj);

} else if (type === "RegExp") {

res = new RegExp(obj);

} else if (type === "Map") {

res = new Map(obj);

} else if (type === "Set") {

res = new Set(obj);

} else if (type === "WeakMap") {

res = new WeakMap(obj);

} else if (type === "WeakSet") {

res = new WeakSet(obj);

}else if (type === "Error") {

res = new Error(obj);

}

else {

res = obj;

}

return res;

}

其实这就是我们最前面提到的第二种方式,很傻对不对,明眼人一眼就能看出来有很多冗余代码可以合并。

我们先进行最基本的优化:

合并冗余代码

将一眼就能看出来冗余的代码合并下。

function deepClone(obj) {

let res = null;

// 类型判断的通用方法

function getType(obj) {

return Object.prototype.toString.call(obj).replaceAll(new RegExp(/[|]|object /g), "");

}

const type = getType(obj);

const reference = ["Set", "WeakSet", "Map", "WeakMap", "RegExp", "Date", "Error"];

if (type === "Object") {

res = {};

for (const key in obj) {

if (Object.hasOwnProperty.call(obj, key)) {

res[key] = deepClone(obj[key]);

}

}

} else if (type === "Array") {

console.log(array obj, obj);

res = [];

obj.forEach((e, i) => {

res[i] = deepClone(e);

});

}

// 优化此部分冗余判断

// else if (type === "Date") {

// res = new Date(obj);

//} else if (type === "RegExp") {

// res = new RegExp(obj);

//} else if (type === "Map") {

// res = new Map(obj);

//} else if (type === "Set") {

// res = new Set(obj);

//} else if (type === "WeakMap") {

// res = new WeakMap(obj);

//} else if (type === "WeakSet") {

// res = new WeakSet(obj);

//}else if (type === "Error") {

// res = new Error(obj);

//}

else if (reference.includes(type)) {

res = new obj.constructor(obj);

} else {

res = obj;

}

return res;

}

为了验证代码的正确性,我们用下面这个数据验证下:

const map = new Map();

map.set("key", "value");

map.set("ConardLi", "coder");

const set = new Set();

set.add("ConardLi");

set.add("coder");

const target = {

field1: 1,

field2: undefined,

field3: {

child: "child",

},

field4: [2, 4, 8],

empty: null,

map,

set,

bool: new Boolean(true),

num: new Number(2),

str: new String(2),

symbol: Object(Symbol(1)),

date: new Date(),

reg: /\d+/,

error: new Error(),

func1: () => {

let t = 0;

console.log("coder", t++);

},

func2: function (a, b) {

return a + b;

},

};

//测试代码

const test1 = deepClone(target);

target.field4.push(9);

console.log(test1: , test1);

执行结果:

还有进一步优化的空间吗?

答案当然是高防服务器肯定的。

// 判断类型的方法移到外部,避免递归过程中多次执行

const judgeType = origin => {

return Object.prototype.toString.call(origin).replaceAll(new RegExp(/[|]|object /g), "");

};

const reference = ["Set", "WeakSet", "Map", "WeakMap", "RegExp", "Date", "Error"];

function deepClone(obj) {

// 定义新的对象,最后返回

//通过 obj 的原型创建对象

const cloneObj = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

// 遍历对象,克隆属性

for (let key of Reflect.ownKeys(obj)) {

const val = obj[key];

const type = judgeType(val);

if (reference.includes(type)) {

newObj[key] = new val.constructor(val);

} else if (typeof val === "object" && val !== null) {

// 递归克隆

newObj[key] = deepClone(val);

} else {

// 基本数据类型和function

newObj[key] = val;

}

}

return newObj;

}

执行结果如下:

Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。返回所指定对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。

这样做的好处就是能够提前定义好最后返回的数据类型。

这个实现参考了网上一位大佬的实现方式,个人觉得理解成本有点高,而且对数组类型的处理也不是特别优雅, 返回类数组。

我在我上面代码的基础上进行了改造,改造后的代码如下:

function deepClone(obj) {

let res = null;

const reference = [Date, RegExp, Set, WeakSet, Map, WeakMap, Error];

if (reference.includes(obj?.constructor)) {

res = new obj.constructor(obj);

} else if (Array.isArray(obj)) {

res = [];

obj.forEach((e, i) => {

res[i] = deepClone(e);

});

} else if (typeof obj === "Object" && obj !== null) {

res = {};

for (const key in obj) {

if (Object.hasOwnProperty.call(obj, key)) {

res[key] = deepClone(obj[key]);

}

}

} else {

res = obj;

}

return res;

}

虽然代码量上没有什么优势,但是整体的理解成本和你清晰度上我觉得会更好一点。服务器租用那么你觉得呢?

最后,还有循环引用问题,避免出现无线循环的问题。

我们用hash来存储已经加载过的对象,如果已经存在的对象,就直接返回。

function deepClone(obj, hash = new WeakMap()) {

if (hash.has(obj)) {

return obj;

}

let res = null;

const reference = [Date, RegExp, Set, WeakSet, Map, WeakMap, Error];

if (reference.includes(obj?.constructor)) {

res = new obj.constructor(obj);

} else if (Array.isArray(obj)) {

res = [];

obj.forEach((e, i) => {

res[i] = deepClone(e);

});

} else if (typeof obj === "Object" && obj !== null) {

res = {};

for (const key in obj) {

if (Object.hasOwnProperty.call(obj, key)) {

res[key] = deepClone(obj[key]);

}

}

} else {

res = obj;

}

hash.set(obj, res);

return res;

}总结

对于深拷贝的实现,可能存在很多不同的实现方式,关键在于理解其原理,并能够记住一种最容易理解和实现的方式,面对类似的问题才能做到 临危不乱,泰然自若。

  • Tag:

相关文章

  • 美的空调投诉及解决方案(解读美的空调投诉流程,助您解决问题)

    摘要:作为一种智能家居产品,空调在我们日常生活中扮演着至关重要的角色。然而,即使是大品牌如美的空调也难免出现一些问题。当您遇到了美的空调的问题并想要进行投诉时,本文将为您提供详细的投诉流...
    2025-11-05
  • 从 LeetCode 的题目再看 MySQL Explain

    本文转载自微信公众号「Java极客技术」,作者鸭血粉丝 。转载本文请联系Java极客技术公众号。今天阿粉主要是想通过 LeetCode 上面的一个题目来再带大家看看 MySQL 的变量使用以及通过 E
    2025-11-05
  • MySQL 实战笔记 第01期:MySQL 角色管理

    角色 ( Role ) 可以用来批量管理用户,同一个角色下的用户,拥有相同的权限。那 MySQL 数据库是否也有这样的功能呢 ?答案是肯定的。MySQL 5.7.X 可以通过 mysql.proxie
    2025-11-05
  • 10年大数据平台经验,总结出这份数据建设干货(内含多张架构图)

    在业务增长过程中,每个企业不知不觉积累积累了一些数据。无论数据是多是少,企业都希望让“数据说话”,通过对数据的采集、存储、分析、计算最终提供对业务有价值信息。由此,大数据平台、数据中台等新鲜的概念就真
    2025-11-05
  • 因特尔赛扬J1800CPU性能评测(高性价比的低功耗处理器)

    摘要:随着科技的飞速发展,计算机硬件的更新换代越来越快。作为计算机的大脑之一,中央处理器的性能对于整体计算机的表现起着举足轻重的作用。本文将重点评测因特尔赛扬J1800CPU的性能,探讨...
    2025-11-05
  • 学透这7个免费SQL项目,目标成为数据库大佬

    SQL可以独立完成数据库生命周期中的全部活动,包括定义关系模式、录入数据、建立数据库、査询、更新、维护、数据库重构、数据库安全性控制等一系列操作,这就为数据库应用系统开发提供了良好的环境,在数据库投入
    2025-11-05

最新评论