EcmaScript对象克隆之谜

先谈谈深拷贝

如何在js中获得一个克隆对象,可以说是喜闻乐见的话题了。相信大家都了解引用类型与基本类型,也都知道有种叫做深拷贝的东西,传说深拷贝可以获得一个克隆对象!那么像我这样的萌新自然就去学习了一波,我们能找到的代码基本都是这样的:

低配版深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var deepClone = function(currobj){if(typeof currobj !== 'object'){return currobj;}if(currobj instanceof Array){var newobj = [];}else{var newobj = {}}for(var key in currobj){if(typeof currobj[key] !== 'object'){newobj[key] = currobj[key];}else{newobj[key] = deepClone(currobj[key])    }}return newobj
}

啧啧真是很精巧啊!对于Array和普通Object都做了区分。但是显然,借助递归实现的深拷贝如果要克隆层级很多的复杂对象,容易造成内存溢出,咱可以做出一个小小改进:

看起来酷一点的深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var deepClone = function(currobj){if(typeof currobj !== 'object'){return currobj;}if(currobj instanceof Array){var newobj = [];}else{var newobj = {}}var currQue = [currobj], newQue = [newobj]; //关键在这里while(currQue.length){var obj1 = currQue.shift(),obj2 = newQue.shift();for(var key in obj1){if(typeof obj1[key] !== 'object'){obj2[key] = obj1[key];}else{if(obj1[key] instanceof Array ){obj2[key] = [];}else{obj2[key] = {}};// 妙啊currQue.push(obj1[key]);newQue.push(obj2[key]);}}}return newobj;
};

这里利用了两个队列,还算优雅的避免了递归的弊端。

JSON序列化

还有一种方法是利用JSON的内置方法,即所谓的JSON序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var deepClone = function(obj){var str, newobj = obj.constructor === Array ? [] : {};if(typeof obj !== 'object'){return;} else if(window.JSON){str = JSON.stringify(obj), //系列化对象newobj = JSON.parse(str); //还原} else {for(var i in obj){newobj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]; }}return newobj;
};

不过不打紧,它与上面方法的效果基本相同。

上面几种深拷贝的局限

拜托,大家都很懂对象,上面的方法有几个很大的问题:

  • 遇到对象内部的循环引用直接gg
  • 无法拷贝函数(typeof 函数 得到的是 ‘function’),函数仍是引用类型
  • 无法正确保留实例对象的原型

于是,我们就要开始改造上面的深拷贝方法来进行完美的克隆了!………….么?

等下,你到底要啥

克隆克隆,我们平常把它挂在嘴上,但面对一个对象,我们真正想克隆的是什么?我想在99%的情况下,我们想克隆的是对象的数据,而保留它的原型引用方法引用,因此上面提到的局限中的第二点,基本可以不考虑。现在咱再来看看怎么解决剩下两点。

解决循环引用

首先搞清什么是循环引用,常见的循环引用有两种:

自身循环引用

1
2
var a = {};
a._self = a;

这种循环引用可以说很是常见。

多个对象互相引用

1
2
3
4
var a = {};
var b = {};
a.brother = b;
b.brother = a;

也不是没见过,不过这是典型导致对象内存无法被回收的写法,本身就不推荐。

解决之道

目前只找到了针对第一种引用的解决方法,来自于Jquery源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
jQuery.extend = jQuery.fn.extend = function() {// options是一个缓存变量,用来缓存arguments[i]// name是用来接收将要被扩展对象的key// src改变之前target对象上每个key对应的value// copy传入对象上每个key对应的valu// copyIsArray判定copy是否为一个数组// clone深拷贝中用来临时存对象或数组的srcvar options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},i = 1,length = arguments.length,deep = false;// 处理深拷贝的情况if (typeof target === "boolean") {deep = target;target = arguments[1] || {};//跳过布尔值和目标 i++;}// 控制当target不是object或者function的情况if (typeof target !== "object" && !jQuery.isFunction(target)) {target = {};}// 当参数列表长度等于i的时候,扩展jQuery对象自身if (length === i) {target = this; --i;}for (; i < length; i++) {if ((options = arguments[i]) != null) {// 扩展基础对象for (name in options) {src = target[name];	copy = options[name];// 防止永无止境的循环,这里举个例子,如var i = {};i.a = i;$.extend(true,{},i);如果没有这个判断变成死循环了if (target === copy) {continue;}if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {if (copyIsArray) {copyIsArray = false;clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是数组的话就让clone副本等于src否则等于空数组。} else {clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是对象的话就让clone副本等于src否则等于空数组。}// 递归拷贝target[name] = jQuery.extend(deep, clone, copy);} else if (copy !== undefined) {target[name] = copy; // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性。}}}}// 返回修改的对象return target;
};

解决原型的引用

在我们想办法魔改深拷贝时,先看下以上这么多深拷贝的基本原理:

利用for-in循环遍历对象属性,如果属性值是对象则深拷贝,不是则直接赋值

于是俺眉头一皱发现事情并不简单,俺上一篇博客已经说明:for-in遍历的是对象以及其原型链上可枚举属性,因此想在遍历时对源对象的__proto__做手脚是根本不存在的,__proto__以及它的不可枚举属性根本不会被遍历到。可以通过下面的例子看出:

1
2
3
4
5
6
7
8
9
10
11
12
13
var deepClone = function() {...} // 随便从上面拿一个
var A = function() {this.val = 1;
}
A.prototype.log = function() {console.log(this.val);
}var obj1 = new A();
var obj2 = deepClone(obj1);console.log(obj1); // A {val: 1}
console.log(obj2); // {val: 1, log: function(){...}}

因此,一个解决方法很单纯,就是像上面的jQuery.extend方法一样,自己传入拷贝的目标对象,extend方法本质上只是拓展目标对象的属性,使其获得源对象上的数据,这样一来只要我们先创建好符合需求的目标对象即可。

另一种方法则是不采用深拷贝,直接取出需要进行拷贝的对象的数据,然后再利用这份数据来实例化和设置一个新的对象出来

1
2
3
4
5
6
7
8
9
10
11
var Foo = function( obj ){this.name = obj.name;this.sex = obj.sex
};Foo.prototype.toJSON = funciton(){return { name: this.name, sex: this.sex };
};var foo = new Foo({ name: "bandit", sex: "male" });
var fooCopy = new Foo( foo.toJSON() );

问题同样得到解决【鼓掌】


回顾一下,没有哪种方法是万用的魔法 —— 在我们想要获得一个克隆对象之前,或许最好先搞清楚我们到底是在克隆什么,再采用最适合的方法。而非是拘泥于“深拷贝浅拷贝”的说法,去复制一段代码祈祷他能生效。我相信以上的示例代码还没有考虑到克隆对象的所有问题,但它们在合适的场景下能够处理合适的问题。嗯,其实很多事情都是这样蛤【带!】

转载于:https://www.cnblogs.com/jinhengyu/p/10257781.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/450784.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开发人员眼中最好的代码编辑器是谁?

摘要&#xff1a;对开发人员来讲&#xff0c;开发工具就好比战场上的“兵器”&#xff0c;不同领域的开发人员他们所使用的“兵器”也不完全相同&#xff0c;本文从友好性、功能性、扩展等多方面总结了最受开发人员欢迎的“兵器”。你最爱的那个在这里吗&#xff1f; 如果我们把…

关于RESTful一些注意事项,接口开发规范

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 最近在研究restful&#xff0c;公司开发要使用&#xff0c;所以自己就去网上找了好些资料&#xff0c;并整理了一套公司开发的接口规范。…

【老杜】MySQL—day01

文章目录day01课堂笔记1、数据库概述及数据准备1.1、什么是数据库1.2、什么是数据库管理系统1.3、SQL概述1.4、安装MySQL数据库管理系统。1.4、MySQL数据库的完美卸载&#xff01;1.5、MySQL的服务1.6、用命令来启动和关闭mysql服务1.7、登录mysql数据库2、MySQL常用命令&#…

【转载】DRuid 大数据分析之查询

转载自http://yangyangmyself.iteye.com/blog/23217591、Druid 查询概述上一节完成数据导入后&#xff0c;接下来讲讲Druid如何查询及统计分析导入的数据。Druid的查询是使用REST风格的HTTP请求查询服务节点&#xff08;Broker、Historical、Realtime&#xff09;&#xff0c;这…

记录 Parameter with that position [1] did not exist; nested exception is java.lang.IllegalArgumentExce

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 报错如题&#xff1a; Parameter with that position [1] did not exist; nested exception is java.lang.IllegalArgumentException: Pa…

[转]简单的动态修改RDLC报表页边距和列宽的方法

本文转自&#xff1a;http://star704983.blog.163.com/blog/static/136661264201161604413204/ 1.修改页边距 XmlDocument XMLDoc new XmlDocument();XMLDoc.Load(System.Windows.Forms.Application.StartupPath "\Report_try-2.rdlc");XmlNamespaceManager xmn n…

函数式编程语言天生就慢吗?

摘要&#xff1a;近期&#xff0c;函数式编程得到了越来越多的关注&#xff0c;Lisp不仅重获青春还涌现出了一批新函数式编程语言。因此开发者们对函数式编程语言的运行快慢各抒己见&#xff0c;展开激烈讨论。本文将和大家一起讨论&#xff0c;函数式编程语言真的就慢吗&#…

【老杜】MySQL—day02

文章目录day02课堂笔记1、把查询结果去除重复记录【distinct】10、连接查询10.1、什么是连接查询&#xff1f;10.2、连接查询的分类&#xff1f;10.3、当两张表进行连接查询时&#xff0c;没有任何条件的限制会发生什么现象&#xff1f;10.4、怎么避免笛卡尔积现象&#xff1f;…

vue根据数组对象中某个唯一标识去重

由于在vue中&#xff0c;会自动在数组和对象中加入_obser__观察者模式的一些属性&#xff0c;所以直接用数组的filter去重&#xff08;下面这种&#xff09;&#xff0c;indexOf不能准确识别 var arr [1, 2, 2, 3, 4, 5, 5, 6, 7, 7]; var arr2 arr.filter(function(x, index…

Springsecurity之AuthenticationProvider

2019独角兽企业重金招聘Python工程师标准>>> 注意&#xff1a;AuthenticationProvider与Authentication紧密联系&#xff0c;关于Authentication&#xff0c;看我的这篇博客。 先上一张图&#xff0c;如下图1 图1 AuthenticationProvider的类图 AuthenticationProvi…

Postman使用入门

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 Postman测试管理的单位是测试集&#xff08;Collections&#xff09;&#xff0c;测试集内可以创建文件夹(Folder)和具体的请求(Requests…

编程需要知道多少数学知识?

摘要&#xff1a;许多人认为在开始学习编程之前必须对数学很在行或者数学分数很高。但一个人为了编程的话&#xff0c;需要学习多少数学呢&#xff1f; 实际上不需要很多 。这篇文章中我会深入探讨编程中所需要的数学知识。 下面是我在reddit的子论坛 r/learnprogramming 看到的…

HDU 6071 Lazy Running

链接HDU 6071 Lazy Running 给出四个点1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;1和2&#xff0c;2和3&#xff0c;3和4&#xff0c;4和1之间有路相连&#xff0c;现在从2点出发&#xff0c;最后回到2点&#xff0c;要求路径大于等于\(K\)&#xff0c;问路径长度最…

vue弹窗插件实战

vue做移动端经常碰到弹窗的需求, 这里写一个功能简单的vue弹窗 popup.vue <template><div class"popup-wrapper" v-show"visible" click"hide"><div class"popup-text">{{text}}</div></div> </temp…

【狂神说】Redis笔记

文章目录1、Nosql概述1.1 为什么要用Nosql1.2 什么是NoSQL1.3 阿里巴巴演进分析2、NoSQL的四大分类3、Redis入门3.1 概述3.2 Windows安装3.3 Linux安装3.4 测试性能3.5 基础的知识4、五大数据类型4.1 Redis-Key4.2 String&#xff08;字符串&#xff09;4.3 List&#xff08;列…

Postman用法说明

见&#xff1a;http://blog.csdn.net/flowerspring/article/details/52774399 Postman用法简介-Http请求模拟工具 在我们平时开发中&#xff0c;特别是需要与接口打交道时&#xff0c;无论是写接口还是用接口&#xff0c;拿到接口后肯定都得提前测试一下&#xff0c;这样的话就…

位、字,字节与KB的关系?

位&#xff1a;我们常说的bit&#xff0c;位就是传说中提到的计算机中的最小数据单位&#xff1a;说白了就是0或者1&#xff1b;计算机内存中的存储都是01这两个东西。 字节&#xff1a;英文单词&#xff1a;&#xff08;byte&#xff09;&#xff0c;byte是存储空间的基本计量…

C++ string 介绍

之所以抛弃char *的字符串而选用C标准程序库中的string类&#xff0c;是因为他和前者比较起来&#xff0c;不必担心内存是否足够、字符串长度等等&#xff0c;而且作为一个类出现&#xff0c;他集成的操作函数足以完成我们大多数情况下(甚至是100%)的需要。我们可以用 进行赋…

Linux核心总结

文章目录1.首先了解一下linux的目录结构2.linux的基本命令之使用命令开关机3.linux的基本命令之目录管理1.ls—列出目录命令2.cd—切换目录命令3.pwd—查看当前所在目录命令4.mkdir—创建文件夹命令5.rmdir—删除文件夹命令6.cp—复制文件命令7.rm—传说中的删库跑路命令8.mv—…