引言

在各个社区查找使用原生js实现深克隆的方法,众说纷纭,大多数实现效果并不理想,在此将各家之言总结一下,得出一个完美的解决方案。

本文参考以下文章,感兴趣者请移步到原文链接。

  • JS类型判断、对象克隆、数组克隆
  • JS中如何进行对象的深拷贝
  • 关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑

浅克隆与深克隆

浅克隆

  • 原始类型为值传递,对象类型仍为引用传递。
  • 原始类型即使我们采用普通的克隆方式仍能得到正确的结果,原因就是原始类型存储的是对象的实际数据。

深克隆

  • 所有元素或属性均完全复制,与原对象完全脱离。
  • 深克隆不是将原对象的引用赋值给新对象,而是新建一个对象,引用地址与原对象引用地址不同,再将原对象属性拷贝到新对象中。

浅克隆的实现探索

数值克隆的实现 -->ok

 const a = 1;let b = a;console.log(b);//1b = 2;console.log(a);// 1console.log(b);// 2
复制代码

字符串克隆的实现 -->ok

  const c="1";let d=c;console.log(d);//"1"d="2";console.log(c);// "1"console.log(d);//"2"
复制代码

布尔值克隆的实现 -->ok

  const x = true;let y = x;console.log(y);//truey = false;console.log(x);// trueconsole.log(y);//false
复制代码

Symbol值克隆的实现 -->ok

  const s1 = Symbol('foo');let s2 = s1;console.log(s2);//Symbol(foo)console.log(s2 === s1);//trues2 = Symbol('bar');console.log(s1, s2); //Symbol(foo) Symbol(bar)console.log(s2 === s1);//false
复制代码

函数克隆的实现 -->ok

  const m = function () {alert(1); };let n = m;n();//1n = function () {alert(2); };m();//1n();//2
复制代码

对象克隆的实现 --> not ok

  let obj = {a: 1,b: 2}let obj2 = obj;console.log(obj);//{a: 1,b: 2}console.log(obj2);//{a: 1,b: 2}obj.a = 8;console.log(obj);//{a: 8,b: 2}console.log(obj2);//{a: 8,b: 2}//obj2 = obj 是值的引用,指向同一个地址值,其中一个发生变化,会影响另一个
复制代码

数组克隆的实现 --> not ok

因为:数组中可以嵌套对象,对象无法实现深克隆,数组也不能。

深克隆的实现探索

经典的 json 方法实现深克隆-->非完美解决方法

  function Person(name) {this.name = name;}const Jack = new Person('Jack');const obj = {a: 1,b: function (arg) {console.log('我是独一无二的,json复制不了我');},c: {d: 3,e: {f: [1,[2,[3,[4,[5]]]]],g: {h: 5}}},date: [new Date(1536627600000), new Date(1540047600000)],reg: new RegExp('\\w+'),num: [NaN, Infinity, -Infinity],person: Jack,};//json方法克隆let obj2 = JSON.parse(JSON.stringify(obj));console.log(obj);console.log(obj2);obj.c.e.f = 1000;//改变源对象的值obj2.c.e.g.h = 2000;//改变克隆对象的值console.log(obj.c.e.f, obj2.c.e.f);console.log( obj.c.e.g.h, obj2.c.e.g.h);
复制代码

运行结果:

由此可知 json 克隆方法有下列缺点:

  1. 如果 obj 里面有时间对象(案例date),则 JSON.stringify 后再 JSON.parse 的结果,时间将只是字符串的形式。而不是时间对象
  2. 如果 obj 里有 RegExp、Error 对象(案例reg),则序列化的结果将只得到空对象
  3. 如果 obj 里有函数(案例b),undefined,则序列化的结果会把函数或 undefined 丢失
  4. 如果 obj 里有 NaN、Infinity 和 -Infinity,则序列化的结果会变成 null
  5. JSON.stringify() 只能序列化对象的可枚举的自有属性,例如 如果 obj 中的对象(案例person)是有构造函数生成的, 则使用 JSON.parse(JSON.stringify(obj)) 深拷贝后,会丢弃对象的 constructor,所有的构造函数会指向 Object
  6. 如果对象中存在循环引用的情况也无法正确实现深拷贝 因此,我们可以得到结论:json 克隆的方法仅适用于简单数据类型的克隆或者仅携带简单数据的引用数据类型。

网络常见的实现深克隆的方法 -->非完美解决方法

  //数据初始化function Person(name) {this.name = name;}const Jack = new Person('Jack');const obj = {a: 1,b: function (arg) {console.log('复制我,你牛逼');},c: {d: 3,e: {f: [1,[2,[3,[4,[5]]]]],g: {h: 5}}},date: [new Date(1536627600000), new Date(1540047600000)],reg: new RegExp('\\w+'),num: [NaN, Infinity, -Infinity],person: Jack,};//深克隆实现方法function deepClone(origin, target) {const tar = target || {};for (let item in origin) {if (origin.hasOwnProperty(item)) {if (typeof origin[item] === 'object') {tar[item] = Object.prototype.toString.call(origin[item]) === '[object Array]' ? [] : {};deepClone(origin[item], tar[item]);} else {tar[item] = origin[item];}}}return tar;}//验证代码let obj2 = deepClone(obj, {});console.log(obj);console.log(obj2);obj.c.e.f = 1000;obj2.c.e.g.h = 2000;console.log(obj.c.e.f, obj2.c.e.f);console.log( obj.c.e.g.h, obj2.c.e.g.h);
复制代码

运行结果:

由结果可知,该函数仍有不足:

  1. 如果obj里面有时间对象(案例date),则得到结果,时间将只是空对象
  2. 如果obj里有RegExp、Error对象(案例reg),则序列化的结果将只得到空对象
  3. 如果obj中的对象(person)是有构造函数生成的, 则使用该深拷贝后,会丢弃对象的constructor;

因此,该函数虽未能完美实现所有类型的深克隆,但对于日常开发足矣,日常开发一般只需要完成对象或数组的克隆,如不考虑时间对象、RegExp、Error对象和由构造函数生成的对象,该方法完全足够。

原生js实现深克隆 -->完美解决方法一

  //数据初始化function Person(name) {this.name = name;}const Jack = new Person('Jack');const obj = {a: 1,b: function (arg) {console.log('复制我,你牛逼');},c: {d: 3,e: {f: [1,[2,[3,[4,[5]]]]],g: {h: 5}}},date: [new Date(1536627600000), new Date(1540047600000)],reg: new RegExp('\\w+'),num: [NaN, Infinity, -Infinity],person: Jack,};//深克隆函数function deepClone(data) {const type = this.judgeType(data);let obj;if (type === 'Array') {obj = [];for (let i = 0, len = data.length; i < len; i++ ) {obj.push(this.deepClone(data[i]));}} else if (type === 'Object') {obj = new data.constructor ();//可保持继承链,解决该问题:如果obj中的对象是由构造函数生成的,则使用深拷贝后,会丢弃对象的constructor;for (let key in data) {obj[key] = this.deepClone(data[key]);//实现深克隆的关键}} else {return data;}return obj;}//判断类型函数function judgeType(obj) {if (obj instanceof Element) {return 'element';}return Object.prototype.toString.call(obj).slice(8,-1);}//验证代码let obj2 = deepClone(obj);console.log(obj);console.log(obj2);obj.c.e.f = 1000;obj2.c.e.g.h = 2000;console.log(obj.c.e.f, obj2.c.e.f);console.log( obj.c.e.g.h, obj2.c.e.g.h);
复制代码

运行结果:

由运行结果可知,该方法完全实现了所有数据类型的深克隆,是解决深克隆的完美方案之一,鉴定完毕!但这代码量是不是多了些?!其实还可以再优化,欢迎大家评论优化。

原生js实现深克隆 -->完美解决方法二

  //数据初始化function Person(name) {this.name = name;}const Jack = new Person('Jack');const obj = {a: 1,b: function (arg) {console.log('复制我,你牛逼');},c: {d: 3,e: {f: [1,[2,[3,[4,[5]]]]],g: {h: 5}}},date: [new Date(1536627600000), new Date(1540047600000)],reg: new RegExp('\\w+'),num: [NaN, Infinity, -Infinity],person: Jack,};//深克隆函数function deepClone(obj) {if (obj === null) return null;if (typeof obj !== 'object') return obj;if (obj.constructor === Date) return new Date(obj);if (obj.constructor === RegExp) return new RegExp(obj);const newObj = new obj.constructor ();  //保持继承链for (let key in obj) {if (obj.hasOwnProperty(key)) {   //不遍历其原型链上的属性const val = obj[key];newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合。}}return newObj;}//判断代码const obj2 = deepClone(obj);console.log(obj);console.log(obj2);obj.c.e.f = 1000;obj2.c.e.g.h = 2000;console.log(obj.c.e.f, obj2.c.e.f);console.log( obj.c.e.g.h, obj2.c.e.g.h);
复制代码

运行结果:

相对于上一个完美解决方法,该方法的实现可圈可点,代码量更少,同样是达到了各种数据类型深克隆的实现,选择哪种方法都是没问题的,看个人喜好罢了。

以上是个人参考众家之言后总结的深克隆方法,如有不正确之处,请各方大家多多指点,谢谢!

转载于:https://juejin.im/post/5c9c74f6f265da610b3a1eff

原生JS如何实现包含各种类型数据的深克隆相关推荐

  1. html 下拉滚动加载,原生js滚动到底部加载数据和下拉刷新 Scrollload

    初衷 如今移动端站点越来越多,滚动到底部加载数据和下拉刷新的需求非常的常见,即使现在很多pc站点也会有这样的需求,比如百度首页就有.虽然简单的完成这么一个功能非常方便,但是滚动往往会成为性能的瓶颈,处 ...

  2. 原生js滚动到底部加载数据和下拉刷新 Scrollload

    原文地址 https://github.com/fa-ge/Scrollload/blob/master/README.md 初衷 如今移动端站点越来越多,滚动到底部加载数据和下拉刷新的需求非常的常见 ...

  3. jsonp原生js跨域拿新浪数据插件封装【可扩展】

    //修改了一个bug,增加了手动释放垃圾 <!DOCTYPE html> <html lang="en"><head><meta char ...

  4. 【第48天】AJAX在原生JS中的使用,处理XML数据以及DBUtils的使用,假删除(标记删除)

    1 介绍 1.1 同步和异步 1.1.1 基于AJAX的异步传输与传统同步传输的对比 1.1.2 使用时间线表示同步和异步在过程上的区别 1.2 JS与AJAX的关系 1.3 AJAX用到的技术 1. ...

  5. 使用WeUI+JS 的label包含input触发两次的问题

    在给原生JS中的包含input标签的Label标签绑定点击的事件会触发两次. 因为使用WeUI样式库,所以不能抛弃label标签,使用event.stopPropagation()似乎没有效果,使用r ...

  6. C#保留2位小数几种场景总结 游标遍历所有数据库循环执行修改数据库的sql命令 原生js轮盘抽奖实例分析(幸运大转盘抽奖) javascript中的typeof和类型判断...

    C#保留2位小数几种场景总结 场景1: C#保留2位小数,.ToString("f2")确实可以,但是如果这个数字本来就小数点后面三位比如1.253,那么转化之后就会变成1.25. ...

  7. JS 打印 data数据_寒冬求职季之你必须要懂的原生JS(中)

    本文原载于SegmentFault专栏"前端进阶" 作者:前端小姐姐 整理编辑:SegmentFault 互联网寒冬之际,各大公司都缩减了HC,甚至是采取了"裁员&quo ...

  8. 进一步封装axios并调用其读取数据(吐槽~在安卓9.0以下或者IOS10.X以下手机端H5页面不支持,在这两种情况下的系统只能使用ajax或者原生js请求后台数据)

    注意!!!(修改于2020年7月18日) 在安卓9.0以下或者IOS10.X以下手机端H5页面不支持,在这两种情况下的系统只能使用ajax或者原生js请求后台数据 报错截图如下 报错内容: {&quo ...

  9. 根据id删除localstorage数据_原生js利用localstorage实现简易TODO list应用

    前言:小生不才,只懂得一些皮毛,我努力以最简单的语言将心中的想法表述出来,让更多人能够很轻松的弄明白.文章里面有不足之处望各位大牛指出,使得后面的文章能够朝着更好的方向发展.另外,大家记得点赞哟! 欢 ...

最新文章

  1. IDEA一定要懂的32条快捷键
  2. Java Web开发中文乱码问题
  3. 查看端口号被哪个程序占用
  4. 【Android】使用AIDL传递用户自定义类型数据--附完整示例代码
  5. PHP的错误机制总结
  6. jdbc链接数据库mysql
  7. Windows性能查看器:系统的性能信息(I/O,IIS最大连接数,Sql) ,以及解决 asp.net IIS 一二百多用户并发...
  8. Android自动化测试之路——技术准备
  9. 输入一个以回车结束的字符串,判断该字符串是否对称(正序与逆序相同,如aBc2cBa为对称字符串)
  10. bat 中 for 的使用 帮助文档 中文版
  11. Sql JOIN 一张图说明
  12. 我的miniQuery
  13. fragment嵌套viewpager嵌套fragment第二次加载数据不显示问题
  14. layui动态渲染生成select的option值
  15. mysql 主从机器 触发器 的测试,完全正常 没有问题
  16. html select 默认不选,解决设置select默认选中不生效的方法
  17. 怎么做国外的问卷调查
  18. 仿真Windows_XP画图板的java实现
  19. Rancher搭建Longhorn分布式存储
  20. Android DVM简介

热门文章

  1. Oracle表空间基础(4)
  2. 关于钥匙串中所有证书签名无效的问题解决纪录
  3. Android 2.3发短信详细流程
  4. [BAT]TASKKILL 杀进程
  5. 安装jdk和oracle要注意的知识点
  6. 测试基础-04-用例的编写评审
  7. Python数据类型之字典
  8. [转]SQL_Server_SSIS_ 最佳实践
  9. 基于 jmeter 的分布式性能测试实战
  10. Linux文件属性与管理