Javascript 深度克隆中的循环引用问题解决和代码优化
概要
在前端项目开发中,我们经常需要深度克隆JS对象。在克隆代码开发过程中,我们经常会遇到数组判定或对象循环引用的问题。
本文通过实例来解决上述问题
代码及实现
常见深度克隆JS对象的代码
function deepClone(origin){if (origin == undefined || typeof origin !== "object"){return origin;}if (origin instanceof Date){return new Date(origin);}let keys = Reflect.ownKeys(origin);let target = {};if (Object.prototype.toString.call(origin) === "[object Array]"){target = [];}for(let key of keys){target[key] = deepClone(origin[key]);}return target;
}
上述代码主要存在两个问题:
- 数组判定代码过于麻烦
- 对象中如果出现循环引用,代码将会报错。
循环引用代码:
var obj = {a: 1};
obj.obj1 = obj;
var newObj = deepClone(obj);
console.log(newObj);
错误信息:
产生错误的原因很简单,由于对象中存在循环引用,所以递归无法结束,最后内存溢出了。
循环引用问题的解决
由于对象中的循环引用,所以递归无法结束。如果我们记录下哪个对象被克隆了,哪个对象没有被克隆,就可以强制结束递归,避免内存泄漏。
我们选择WeekMap来记录对象是否被克隆,主要考虑以下三点:
- WeakMap对象是key => value形式,不会重复记录
- WeakMap对象的key必须是一个对象
- WeakMap是若引用,如果不再使用,内存空间直接释放。
改进代码如下:
function deepClone(origin, map = new WeakMap()){if (origin == undefined || typeof origin !== "object"){return origin;}if (origin instanceof Date){return new Date(origin);}if (origin instanceof RegExp){return new RegExp(origin);}var copied = map.get(origin);if (!!copied){return copied;} let target = {};if (Object.prototype.toString.call(origin) === "[object Array]"){target = [];}map.set(origin, target);let keys = Reflect.ownKeys(origin);for(let key of keys){ target[key] = deepClone(origin[key], map);}return target;
}
- 通过WeakMap记录对象是否被拷贝过,如果拷贝过,就直接将拷贝过的对象返回,不再重复克隆
- 如果没有被拷贝过,构建数组或普通JS对象,然后再进行记录。
克隆存在循环引用的对象,代码如下:
var obj = {a: 1};
obj.obj1 = obj;
var newObj = deepClone(obj);
console.log(newObj);
执行结果如下:
数组判定的代码优化
在克隆之前,我们必须知道要克隆的对象是一个普通JS对象还是一个数组。我们是否可以换一个思路,任何对象都有自己的构建器,如果我们直接使用构造器构造对象,也就不需要之前对象是什么类型了。
JS中所有对象都有自己的原型链,原型链中包含基类Object的构造器,如下所示:
JS普通对象
JS数组:
JS普通对象的构造方法可以构建出{},JS数组的构建方法可以构造出[]。所以如果我们调用该构造方法,就无需再去判定数组还是普通对象了。
改进后的代码如下:
function deepClone(origin, map = new WeakMap()){if (origin == undefined || typeof origin !== "object"){return origin;}if (origin instanceof Date){return new Date(origin);}var copied = map.get(origin);if (!!copied){return copied;} let target = new origin.constructor();map.set(origin, target);let keys = Reflect.ownKeys(origin);for(let key of keys){ target[key] = deepClone(origin[key], map);}return target;
}
Javascript 深度克隆中的循环引用问题解决和代码优化相关推荐
- 小记 TypeScript 中的循环引用问题
随着项目规模的不断增长,循环引用问题似乎总是不可避免,本文就 TypeScript 中可能出现的循环引用问题做了一些简单记录~ 平时编写 TypeScript 代码时,一般都倾向于使用模块(Modul ...
- Swift之常见闭包与defer关键字的使用分析和闭包中的循环引用 | CSDN创作打卡
一.什么是闭包? 在 Swift 中,可以通过 func 定义一个函数,也可以通过闭包表达式定义一个函数,闭包是一个捕获了上下文的常量或者是变量的函数.闭包(Closures)是自包含的功能代码块,可 ...
- javascript 深度克隆对象
js一般有两种不同数据类型的值: 基本类型(包括undefined,Null,boolean,String,Number),按值传递: 引用类型(包括数组,对象),按址传递,引用类型在值传递的时候是内 ...
- 覆盖所有类型的javascript深度克隆
第一种方法(只适用于基础类型) const newObj = JSON.parse(JSON.stringify(oldObj)); 第二种方法 const getType = (obj)=> ...
- nstimer循环引用_ios开发中经典循环引用场景?
1.属性传值循环引用 如:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexP ...
- 【FastJSON】解决FastJson中“$ref 循环引用”的问题
0.开发环境 SSH,EasyUI,MySQL 1.需求要求: (1)首先获取所有的贷款订单数据,即List <LoanOrder>. (2)然后从单个贷款订单实体LoanOrder去访问 ...
- 解决FastJson中“$ref 循环引用检测”的问题的几种方式
一.现象: 项目中用json形式来存储一个集合对象,用fastjson发现多了一些东西:$ref,了解之后才发现是重复引用的问题. [{"id":"1",&qu ...
- 深入理解C++中的循环引用问题及解决方法
循环引用问题在C++中是指当两个或多个对象互相持有对方的引用(通常是通过智能指针),导致它们的引用计数永远不会降为零,从而导致内存泄漏的情况.这种问题在使用shared_ptr时尤为突出,因为shar ...
- 防止iOS中私有属性在block中的循环引用
想看答案可以直接瞅瞅底下代码. 对于一般的@property修饰的属性我们可以使用__weak转换一下self来修饰 __weak typeof(self) weakSelf = self;//然后把 ...
最新文章
- 浅谈 sessionStorage、localStorage、cookie 的区别以及使用
- 浅尝key-value数据库(三)——MongoDB的分布式
- php获取总共内存_php获取页面运行使用内存的两个函数
- 【已解决】单片机串口通讯中RXD与TXD如何连线?
- Refactor?or Patching?
- ffmpeg - AVPacket内存问题分析(AVFrame一样的)
- grasp设计模式应用场景_grasp设计模式笔记回顾
- hive建表设置如果为null_Hive表中的NULL值处理
- 【java】创建一个JFrame,可以使得一个字符串用按钮进行颜色的选择
- python获取文件名中两条下划线之间的部分_Python 中的特殊双下划线方法
- dubbo注入找不到bean_Dubbo配置参考手册之dubbo:reference
- 【粗解】【通信编码】卷积编码器的简单实现
- Kaggle 机器学习实战 朴素贝叶斯(原理+西瓜数据集实战)
- 2017年6月14日中午 java.io.IOException: Premature EOF
- 【转载】mac读取ntfs硬盘方法
- BigDecimal,BigInteger 学习以及简单示例
- Rockchip CAN 总线
- java graphics2d旋转_反向Java Graphics2D缩放和旋转坐标
- C语言解析wav文件格式
- Logisticregression学习
热门文章
- 快速导入Android项目
- 中国 GPRS接入点名称
- 批量给项目的java文件添加licence文件头
- 计算机教室cad平面图,教室的平面图怎么画
- SAP物料移动类型和自动科目设置(包含财务,pp) 二
- 在线支付系列【23】支付宝支付接入指南
- Linux常用命令——strace命令
- http请求 url 竖线_HTML 里竖线 这个“ | ” 符号怎么弄?谢谢解答。
- 服务器2400系列和2600系列,2400g与2600的CPU性能相差多少?最新游戏实测对比
- 已提交内存过大_虚拟内存别关!?!