JS手写实现深拷贝

2024-09-08 04:36
文章标签 实现 js 拷贝 手写

本文主要是介绍JS手写实现深拷贝,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

手写深拷贝

  • 一、通过JSON.stringify
  • 二、函数库lodash
  • 三、递归实现深拷贝
    • 基础递归
    • 升级版递归---解决环引用爆栈问题
    • 最终版递归---解决其余类型拷贝结果

一、通过JSON.stringify

JSON.parse(JSON.stringify(obj))是比较常用的深拷贝方法之一

原理:利用JSON.stringify 将JavaScript对象序列化成为JSON字符串,并将对象里面的内容转换成字符串,再使用JSON.parse来反序列化,将字符串生成一个新的JavaScript对象

这个方法是目前使用最多的深拷贝的方法,也是最简单的方法,使用示例:

let obj1 = {  a: 0,b: {c: 0}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

这个方法虽然简单粗暴,但也存在一些问题,在使用该方法时需要注意:

拷贝的对象中如果有 function、undefined、symbol,当使用过JSON.stringify()进行处理之后,都会消失。

const originObj = {name: 'test',age: undefined,func: function () {console.log('Hello World');},key: Symbol('一个独一无二的key')
}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // 只剩下 {name: "test"}
  • 无法拷贝不可枚举的属性;
  • 无法拷贝对象的原型链;
  • 拷贝 Date 引用类型会变成字符串;
  • 拷贝 RegExp 引用类型会变成空对象;
  • 对象中含有NaN、Infinity以及 -Infinity,JSON 序列化的结果会变成null;
  • 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。

二、函数库lodash

该函数库也有提供_.cloneDeep用来做深拷贝,可以直接引入并使用:

var _ = require('lodash');
var obj1 = {a: 1,b: { f: { g: 1 } },c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false

三、递归实现深拷贝

实现深拷贝的思路就是,使用for in来遍历传入参数的属性值

  • 如果值是基本类型就直接复制
  • 如果是引用类型就进行递归调用该函数

基础递归

function deepClone (source) {//判断source是不是对象if (source instanceof Object === false) return source;//根据source类型初始化结果变量let target = Array.isArray(source) ? [] : {};for (let i in source) {// 判断是否是自身属性if (source.hasOwnProperty(i)) {//判断数据i的类型if (typeof source[i] === 'object') {target[i] = deepClone(source[i]);} else {target[i] = source[i];}}}return target;
}const obj = {info: { c: { d: 1 } },age: undefined,func: function () {console.log('Hello World');},key: Symbol('一个独一无二的key')
}const resultA = deepClone(obj)
​
obj.info.c.d = 3
console.log(111111, obj); 
// { info: { c: { d: 3 } }, age: undefined, func: f, key: xxxxx}
​
console.log(222222, resultA); 
// { info: { c: { d: 1 } }, age: undefined, func: f, key: xxxxx}
​
​
let resultB = [1, [2, 3], [4, [5]]]
let resultC = deepClone(resultB)
​
resultB[1][1] = 7
console.log(333333, resultB);  // [1, [2, 7], [4, [5]]]
​
console.log(444444, resultC);  // [1, [2, 3], [4, [5]]]

这只是粗略的版本,这样虽然实现了深拷贝,但也存在一些问题:

  • 存在环引用问题(存在循环引用,拷贝会直接爆栈)
    在这里插入图片描述
  • 对于Date、RegExp、Set、Map等引用类型不能正确拷贝

升级版递归—解决环引用爆栈问题

function deepClone (source, map = new Map()) {//判断source是不是对象if (source instanceof Object === false) return source;//根据source类型初始化结果变量let target = Array.isArray(source) ? [] : {};/* ----------------新增---------------- */if (map.get(source)) {// 已存在则直接返回return map.get(source)}// 不存在则第一次设置map.set(source, target)/* ----------------新增---------------- */for (let i in source) {// 判断是否是自身属性if (source.hasOwnProperty(i)) {//判断数据i的类型if (typeof source[i] === 'object') {// 传递maptarget[i] = deepClone(source[i], map);} else {target[i] = source[i];}}}return target;
}const obj = {info: { c: { d: 1 } },age: undefined,func: function () {console.log('Hello World')},key: Symbol('一个独一无二的key'),
}// 形成环引用
obj.loop = obj
console.log(obj);const resultA = deepClone(obj)
console.log(resultA); // 拷贝成功

最终版递归—解决其余类型拷贝结果

// 可遍历类型
const arrTag = '[object Array]';
const objTag = '[object Object]';
const mapTag = '[object Map]';
const setTag = '[object Set]';
const argTag = '[object Arguments]';
const strTag = '[object String]';// 不可遍历类型
const boolTag = '[object Boolean]';
const numTag = '[object Number]';
const dateTag = '[object Date]';
const errTag = '[object Error]';
const regexpTag = '[object RegExp]';
const symbolTag = '[object Symbol]';
const funTag = '[object Function]';// 将可遍历类型做个集合
const traversalArr = [arrTag, objTag, mapTag, setTag, argTag, strTag];
​
​
// 判断类型的函数(采用最全且无遗漏的判断方式)
function checkType (source) {return Object.prototype.toString.call(source)
}// 拷贝RegExp的方法
function cloneReg (source) {const reFlags = /\w*$/;const result = new source.constructor(source.source, reFlags.exec(source));result.lastIndex = source.lastIndex;return result;
}// 拷贝Date的方法
function cloneDate (source) {return new source.constructor(source.valueOf())
}
​
​
function deepClone (source, map = new Map()) {// 非对象直接返回if (source instanceof Object === false) return source
​// 根据source类型初始化结果变量let target = Array.isArray(source) ? [] : {};
​
​/* ----------------处理环引用问题---------------- */// 已存在则直接返回(仅仅在环引用之间生效)if (map.get(source)) return map.get(source)// 不存在则第一次设置map.set(source, target)/* ----------------处理环引用问题---------------- */
​
​/* ----------------处理Map、Set、Date、RegExp深拷贝失效问题---------------- */const type = checkType(source)
​console.log(type);let emptyObj
​// 如果是可遍历类型,直接创建空对象if (traversalArr.includes(type)) {emptyObj = new source.constructor()}// 处理Map类型if (type === mapTag) {source.forEach((value, key) => {emptyObj.set(key, deepClone(value, map))})return emptyObj}// 处理Set类型if (type === setTag) {source.forEach(value => {emptyObj.add(deepClone(value, map))})return emptyObj}// 处理Date类型if (type === dateTag) return cloneDate(source)// 处理Reg类型if (type === regexpTag) return cloneReg(source)/* ----------------处理Map、Set、Date、RegExp深拷贝失效问题--------------- */
​
​for (let item in source) {// 判断是否是自身属性if (source.hasOwnProperty(item)) {// 判断数据i的类型// if (source[item] instanceof Object) {if (typeof source[item] === 'obejct') {target[item] = deepClone(source[item], map);} else {target[item] = source[item];}}}return target;
}const obj = {// 基本类型str: 'test',num: 18,boolean: true,sym: Symbol('独一无二key'),// 引用类型(以下8种数据对象均需进行真正意义上的深拷贝)obj_object: { name: 'squirrel' },arr: [123, '456'],func: (name, age) => console.log(`姓名:${name},年龄:${age}`),map: new Map([['t', 100], ['s', 200]]),set: new Set([1, 2, 3]),date: new Date(),reg: new RegExp(/test/g),
}// 形成环引用
obj.loop = obj
​
const result = deepClone(obj)
console.log('手写deepClone结果:', result)

结果如下(完美解决了基础递归中的问题):
请添加图片描述

这篇关于JS手写实现深拷贝的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1147126

相关文章

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

idea中创建新类时自动添加注释的实现

《idea中创建新类时自动添加注释的实现》在每次使用idea创建一个新类时,过了一段时间发现看不懂这个类是用来干嘛的,为了解决这个问题,我们可以设置在创建一个新类时自动添加注释,帮助我们理解这个类的用... 目录前言:详细操作:步骤一:点击上方的 文件(File),点击&nbmyHIgsp;设置(Setti

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

MySQL大表数据的分区与分库分表的实现

《MySQL大表数据的分区与分库分表的实现》数据库的分区和分库分表是两种常用的技术方案,本文主要介绍了MySQL大表数据的分区与分库分表的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. mysql大表数据的分区1.1 什么是分区?1.2 分区的类型1.3 分区的优点1.4 分

一文详解如何从零构建Spring Boot Starter并实现整合

《一文详解如何从零构建SpringBootStarter并实现整合》SpringBoot是一个开源的Java基础框架,用于创建独立、生产级的基于Spring框架的应用程序,:本文主要介绍如何从... 目录一、Spring Boot Starter的核心价值二、Starter项目创建全流程2.1 项目初始化(

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

mysql数据库重置表主键id的实现

《mysql数据库重置表主键id的实现》在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,本文主要介绍了mysql数据库重置表主键id的实现,具有一定的参考价值,感兴趣的可以了... 目录关键语法演示案例在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,当我们