js的浅复制和深复制
1.浅复制VS深复制
本文中的复制也可以称为拷贝,在本文中认为复制和拷贝是相同的意思。另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,undefined,string,number和boolean),这些类型的值本身就存储在栈内存中(string类型的实际值还是存储在堆内存中的,但是js把string当做基本类型来处理 ),不存在引用值的情况。
浅复制和深复制都可以实现在已有对象的基础上再生一份的作用,但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此复制的时候就存在两种情况了:复制引用和复制实例,这也是浅复制和深复制的区别所在。
浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响
深复制:深复制不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深复制的对象的引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是完全隔离的
由深复制的定义来看,深复制要求如果源对象存在对象属性,那么需要进行递归复制,从而保证复制的对象与源对象完全隔离。然而还有一种可以说处在浅复制和深复制的粒度之间,也是jQuery的extend方法在deep参数为false时所谓的“浅复制”,这种复制只进行一个层级的复制:即如果源对象中存在对象属性,那么复制的对象上也会引用相同的对象。这不符合深复制的要求,但又比简单的复制引用的复制粒度有了加深。
2. 浅复制
本文认为浅复制就是简单的引用复制,这种情况较很简单,通过如下代码简单理解一下:
|
1
2
3
4
5
6
7
|
var src = { name:"src" } //复制一份src对象的应用 var target = src; target.name = "target"; console.log(src.name); //输出target |
target对象只是src对象的引用值的复制,因此target的改变也会影响src。
3. 深复制
深复制的情况比较复杂一些,我们先从一些比较简单的情况说起:
3.1 Array的slice和concat方法
Array的slice和concat方法都会返回一个新的数组实例,但是这两个方法对于数组中的对象元素却没有执行深复制,而只是复制了引用了,因此这两个方法并不是真正的深复制,通过以下代码进行理解:
|
1
2
3
4
5
6
7
|
var array = [1,2,3];var array_shallow = array;var array_concat = array.concat();var array_slice = array.slice(0);console.log(array === array_shallow); //trueconsole.log(array === array_slice); //falseconsole.log(array === array_concat); //false |
可以看出,concat和slice返回的不同的数组实例,这与直接的引用复制是不同的。
|
1
2
3
4
5
6
7
8
9
10
11
|
var array = [1, [1,2,3], {name:"array"}];var array_concat = array.concat();var array_slice = array.slice(0);//改变array_concat中数组元素的值array_concat[1][0] = 5;console.log(array[1]); //[5,2,3]console.log(array_slice[1]); //[5,2,3]//改变array_slice中对象元素的值array_slice[2].name = "array_slice";console.log(array[2].name); //array_sliceconsole.log(array_concat[2].name); //array_slice |
通过代码的输出可以看出concat和slice并不是真正的深复制,数组中的对象元素(Object,Array等)只是复制了引用
3.2 JSON对象的parse和stringify
JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深复制。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var source = { name:"source", child:{ name:"child" }}var target = JSON.parse(JSON.stringify(source));//改变target的name属性target.name = "target";console.log(source.name); //sourceconsole.log(target.name); //target//改变target的childtarget.child.name = "target child";console.log(source.child.name); //childconsole.log(target.child.name); //target child |
从代码的输出可以看出,复制后的target与source是完全隔离的,二者不会相互影响。
这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理
3.3 jQuery中的extend复制方法
jQuery中的extend方法可以用来扩展对象,这个方法可以传入一个参数:deep(true or false),表示是否执行深复制(如果是深复制则会执行递归复制),我们首先看一下jquery中的源码(1.9.1)
|
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
59
60
61
62
63
|
jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target;}; |
这个方法是jQuery中重要的基础方法之一,可以用来扩展jQuery对象及其原型,也是我们编写jQuery插件的关键方法,事实上这个方法基本的思路就是如果碰到array或者object的属性,那么就执行递归复制,这也导致对于Date,Function等引用类型,jQuery的extend也无法支持。下面我们大致分析一下这个方法:
(1)第1-6行定义了一些局部变量,这些局部变量将在以后用到,这种将函数中可能用到的局部变量先统一定义好的方式也就是“单var”模式
(2)第9-13行用来修正deep参数,jQuery的这个方法是将deep作为第一个参数传递的,因此这里就判断了第一个参数是不是boolean类型,如果是,那么就调整target和i值,i值表示第一个source对象的索引
(3)第17-19行修正了target对象,如果target的typeof操作符返回的不是对象,也不是函数,那么说明target传入的是一个基本类型,因此需要修正为一个空的对象字面量{}
(4)第22-25行来处理只传入了一个参数的情况,这个方法在传入一个参数的情况下为扩展jQuery对象或者其原型对象
(5)从27行开始使用for in去遍历source对象列表,因为extend方法是可以传入多个source对象,取出每一个source对象,然后再嵌套一个for in循环,去遍历某个source对象的属性
(6)第32行分别取出了target的当前属性和source的当前属性,35-38行的主要作用在于防止深度遍历时的死循环。然而如果source对象本身存在循环引用的话,extend方法依然会报堆栈溢出的错误
(7)第41行的if用来处理深复制的情况,如果传入的deep参数为true,并且当前的source属性值是plainObject(使用对象字面量创建的对象或new Object()创建的对象)或数组,则需要进行递归深复制
(8)第42-48根据copy的类型是plainObject还是Array,对src进行处理:如果copy是数组,那么src如果不是数组,就改写为一个空数组;如果copy是chainObject,那么src如果不是chainObject,就改写为{}
(9)如果41行的if条件不成立,那么直接把target的src属性用copy覆盖
jQuery的extend方法使用基本的递归思路实现了深度复制,但是这个方法也无法处理source对象内部循环引用的问题,同时对于Date、Function等类型的值也没有实现真正的深度复制,但是这些类型的值在重新定义时一般都是直接覆盖,所以也不会对源对象造成影响,因此一定程度上也符合深复制的条件
3.4 自己实现一个copy方法
根据以上的思路,自己实现一个copy,可以传入deep参数表示是否执行深复制:
|
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
|
//util作为判断变量具体类型的辅助模块 var util = (function(){ var class2type = {}; ["Null","Undefined","Number","Boolean","String","Object","Function","Array","RegExp","Date"].forEach(function(item){ class2type["[object "+ item + "]"] = item.toLowerCase(); }) function isType(obj, type){ return getType(obj) === type; } function getType(obj){ return class2type[Object.prototype.toString.call(obj)] || "object"; } return { isType:isType, getType:getType } })(); function copy(obj,deep){ //如果obj不是对象,那么直接返回值就可以了 if(obj === null || typeof obj !== "object"){ return obj; } //定义需要的局部变脸,根据obj的类型来调整target的类型 var i, target = util.isType(obj,"array") ? [] : {},value,valueType; for(i in obj){ value = obj[i]; valueType = util.getType(value); //只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制 if(deep && (valueType === "array" || valueType === "object")){ target[i] = copy(value); }else{ target[i] = value; } } return target; } |
js的浅复制和深复制的更多相关文章
- 也来谈一谈js的浅复制和深复制
1.浅复制VS深复制 本文中的复制也可以称为拷贝,在本文中认为复制和拷贝是相同的意思.另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,unde ...
- js中的浅复制和深复制
浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响 深复制:深复制不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深复 ...
- 深度解析javascript中的浅复制和深复制
原文:深度解析javascript中的浅复制和深复制 在谈javascript的浅复制和深复制之前,我们有必要在来讨论下js的数据类型.我们都知道有Number,Boolean,String,Null ...
- iOS 浅复制和深复制的深层理解,含示例
转载:https://www.zybuluo.com/MicroCai/note/50592 版权归 @MicroCai 所有 以下是正文: 浅复制就是指针拷贝:深复制就是内容拷贝. 集合的浅复制 ( ...
- java基础-浅复制与深复制的理解
浅复制与深复制在很多编程语言中都有出现,那么什么是浅复制,什么是深复制呢? 要区分浅复制与深复制,首先我们要明确什么是复制,怎样才算是复制.复制的例子在生活中也随处可见,如复印一份文档,复制一段文字等 ...
- iOS 浅复制、深复制、完全复制的知识点梳理验证(附加归档解档)
在之前转载的一片文章中,文中对浅复制和深复制进行了详细的解读,同时还提到了深复制(one-level-deep copy).完全复制(true copy)的概念,并指出iOS开发中的深复制是单层深赋值 ...
- JAVA中浅复制与深复制 - coolmist - ITeye技术网站
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
- Java中引用的浅复制和深复制
Java中除了基本类型int,char,double等的赋值是按照值传递之外,其余的类型和对象都是按照引用进行传递的. 下面来看一个关于引用的例子. package referenceCopy;// ...
- Java里的浅复制与深复制
1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不 复制它所引用的对象. ...
随机推荐
- 论EFMS模拟量部分采集电路的修改
论1:电阻R11的作用 如图1是2014-3-11之前模拟量采集的部分硬件电路,图2是纠正后的正确电路. D5是SA20CA,TVS双向二极管,有效防止外接电源的浪涌冲击情况,保护电路. D6是稳压 ...
- centos下网口vlan设置
如果要使vlan之间进行通信,我们通常会使用三层交换机或者路由器子接口模式来做.Linux上关于VLAN与Cisco交换机中继连接,也是可以实现其互相之间的通信的. 环境:RHEL 5.2 最小化安装 ...
- [FMX]获取控件样式中的指定项目以便进行调节
[FMX]获取控件样式中的指定项目以便进行调节 2017-03-26 • C++ Builder.Delphi.教程 • 暂无评论 • swish •浏览 650 次 FMX 的样式丰富了我们的设计, ...
- Android-ANR异常
A N R : Application Not Responding : 应用没有响应异常 引发ANR的原因是,组件的主线程去执行了耗时操作多少秒,就会引发ARN异常: 引发ANR异常的案例: pac ...
- EF学习笔记-2 EF之支持复杂类型的实现
使用过.NET的小伙伴们知道,在我们的实体模型中,除了一些简单模型外,还有一些复杂类型,如几个简单的类型组合而成的类型:而EF除了在实现基本的增删改查之外,也支持复杂类型的实现. 那么如何手动构造复杂 ...
- Web安全测试学习手册-业务逻辑测试
i春秋作家:Vulkey_Chen 首先感谢朋友倾璇的邀请 http://payloads.online/archivers/2018-03-21/1 ,参与了<web安全测试学习手册>的 ...
- iOS UIFileSharingEnabled
一.让iOS App通过iTunes进行文件交换Documents 让iOS App通过iTunes进行文件交换 有一些App需要通过使用iTunes让用户上传和下载文档.要让iOS程序支持iTune ...
- EFCore.MySql当模型遇到int[]怎么办
我使用的是Pomole.EntityFrameworkCore.MySql 需要将旧项目中的excels表转成实体,其中有一列是json格式的int[] 当遇到第一张表的时候,我使用了这样的方法来读取 ...
- 课程一(Neural Networks and Deep Learning),第四周(Deep Neural Networks) —— 3.Programming Assignments: Deep Neural Network - Application
Deep Neural Network - Application Congratulations! Welcome to the fourth programming exercise of the ...
- Android使用Xutil3.0下载文件.md
Android使用Xutil3.0下载文件.md 新建项目: 引入依赖: implementation 'org.xutils:xutils:3.5.0' 添加权限: <uses-permiss ...