js深浅复制(拷贝)
从两种数据类型说起
在js中,变量的类型可以大致分成两种:基本数据类型和引用数据类型,其中基本数据类型指的是简单的数据段,包括:
UndefinedNullBooleanNumberString(字符串在一些其他语言中是被当做对象使用的,属于引用类型,但在js里是基本类型)
而引用类型的值指的是可能包含多个值的对象。可能上面这种描述大家都看过不少,但是有没有思考过为什么要把数据类型这样分呢?本质上,是因为基本数据类型保存在栈内存,而引用类型保存在堆内存中。那再进一步问:为什么要分两种保存方式呢? 根本原因在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是我们可以把它的地址写在占内存中以供我们访问。举个例子:
var a = 1;//定义了一个number类型
var obj1 = {//定义了一个objr类型
name:'obj'
};
在执行这段代码后,内存空间里是这样的:
因为这种保存方式的存在,所以我们在操作变量的时候,如果是基本数据类型,则按值访问,操作的就是变量保存的值;如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址类操作实际对象。从而也引出了所谓的深浅复制问题。
深复制和浅复制
不同的复制方式
紧接着上文的内容,假设有以下代码:
//例子1
var a = 1;
var b = a;//复制
console.log(b)//1
a = 2;//改变a的值
console.log(b)//1
可以看到,我们复制完b以后,即使改变a的值,b也不会改变,因为a和b是相互独立的,给b赋值的时候是在a值改变之前,按照上面的图,也就是在栈内存中创建了一个变量b 保存的值也是2;
//例子2
var color1 = ['red','green'];
var color2 = color1;//复制
console.log(color2)//['red','green'];
color1.push('black') ;//改变color1的值
console.log(color2)//['red','green','black']
在例子2中,我们按照完全相同的步骤,操作了一个数组,但是返回的结果却完全不一样,因为此时的复制,实际上是这样:
我们只是复制了一次引用类型的地址而已,所以,不管接下来我们是操作color1还是color2,本质上都是操作同一个数组对象。
深复制和浅复制
刚刚说到,简单的赋值没有办法复制引用类型,那如果我们就是想复制上面的color1数组怎么办呢?可以这样:
var color1 = ['red','green'];
var color2 = [];
//复制
for(var i = 0;i < color1.length;i++){
color2[i] = color1[i];
}
console.log(color2)//['red','green'];
color1.push('black') ;//改变color1的值
console.log(color2)//['red','green']
这一次我们先创建了一个空数组color2,然后让color2的每个值都和color1对应相等,最后的color1和color2是相互独立的了,满足了我们的需要。当然对于对象类型也是一样的,使用for-in遍历取代这里的for循环即可。
问题真的就这样解决了吗?当然没有,不过以上这种只复制了第一层属性的方式就叫做浅复制,浅复制有什么缺陷呢?我们可以先思考一下,从直接使用=符号赋值进行复制到浅复制,能够复制成功(成功是指复制的结果与复制源完全独立),是因为我们复制的对象都是基本类型,怎么解释呢?
- 在复制基本数据类型时,我们直接使用
=完成复制 - 在引用类型的时候,我们循环遍历对象,对每个属性或值使用
=完成复制
有没有注意到上文的color1例子使用浅复制之所以能够复制成功,是因为数组中的每一项都是基本数据类型(string),所以猜出了浅复制的局限了吗?假如数组中某一项保存的是一个对象,或者是一个数组,又或者对象的某个属性还是一个对象呢?(换句话说就是引用类型的某个属性还是引用类型),如:
var person = {
name:'lin',
score:{
physics:85,
math:99
}
}
这个对象的分数score属性就还是一个对象,那我们使用前面提到for-in遍历复制的时候,对score的复制,不就又变成了我们前面提到的只复制了地址的情况吗?再想想浅复制实现的原理,相信大家猜到了深复制实现的方式:对属性中所有引用类型的值,遍历到是基本类型的值为止,从这种方式上,我们很容易就可以想到利用递归来实现深复制。
function deepCopy (obj) {
var result;
//引用类型分数组和对象分别递归
if (Object.prototype.toString.call(obj) == '[object Array]') {
result = []
for (i = 0; i < obj.length; i++) {
result[i] = deepCopy(obj[i])
}
} else if (Object.prototype.toString.call(obj) == '[object Object]') {
result = {}
for (var attr in obj) {
result[attr] = deepCopy(obj[attr])
}
}
//值类型直接返回
else {
return obj
}
return result
}
上面的函数很简单:对于传入的参数,首先判断是否为引用类型,如果不是,直接返回即可;如果是,循环遍历该对象的属性,如果某个属性还是引用类型,则针对该属性再次调用deepCopy函数,从而完成深复制。
附注
对于浅复制,其实还有其他的实现方式,比如数组中concat和slice方法,对于这些还是希望大家自己了解,本本主要针对深浅复制的实现原理进行解析。
小结
对于深浅复制的区别,其实核心的关键点就是是只复制了第一属性还是完全复制了所有的属性
js深浅复制(拷贝)的更多相关文章
- 详谈OC(object-c)深浅复制/拷贝-什么情况下用retain和copy
读前小提示:对于深浅复制有一个清楚的了解,对于学习oc的朋友来说,至关重要.那么首先,我们要明白深浅复制是如何定义的呢.这里为了便于朋友们理解,定义如下. 浅 复 制:在复制操作时,对于被复制的对象的 ...
- js深浅复制
一.数组的深浅拷贝 <body> <script type="text/javascript"> var arr = ["One",&q ...
- OC-深浅复制
[OC学习-26]对象的浅拷贝和深拷贝——关键在于属性是否可被拷贝 对象的拷贝分为浅拷贝和深拷贝, 浅拷贝就是只拷贝对象,但是属性不拷贝,拷贝出来的对象和原来的对象共用属性,即指向同一个属性地址. 深 ...
- Objective-c 深浅复制
深浅复制的定义: 浅复制:在复制时,对于被复制对象的每一层都是指针复制. 深复制:在复制时,对于被复制的对象至少有一层是对象复制. 完全复制:在复制时,对于被复制对象的每一层都是完全复制. retai ...
- JS对象复制(深拷贝、浅拷贝)
如何在 JS 中复制对象 在本文中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个方面,介绍多种 JS 中复制对象的方法. 在开始之前,有一些基础知识值得一提:Javas ...
- C#设计模式(6)——原型模式(Prototype Pattern) C# 深浅复制 MemberwiseClone
C#设计模式(6)——原型模式(Prototype Pattern) 一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创 ...
- C# Json反序列化 C# 实现表单的自动化测试<通过程序控制一个网页> 验证码处理类:UnCodebase.cs + BauDuAi 读取验证码的值(并非好的解决方案) 大话设计模式:原型模式 C# 深浅复制 MemberwiseClone
C# Json反序列化 Json反序列化有两种方式[本人],一种是生成实体的,方便处理大量数据,复杂度稍高,一种是用匿名类写,方便读取数据,较为简单. 使用了Newtonsoft.Json,可以自 ...
- 【IPHONE开发-OBJECTC入门学习】复制对象,深浅复制
转自:http://blog.csdn.net/java886o/article/details/9046273 #import <Foundation/Foundation.h> int ...
- JavaScript的深浅复制
JavaScript的深浅复制 为什么有深复制.浅复制? JavaScript中有两种数据类型,基本数据类型如undefined.null.boolean.number.string,另一类是Obje ...
- 【转】js实现复制到剪贴板功能,兼容所有浏览器
两天前听了一个H5的分享,会议上有一句话,非常有感触:不是你不能,而是你对自己的要求太低.很简单的一句话,相信很多事情不是大家做不到,真的是对自己的要求太低,如果对自己要求多一点,那么你取得的进步可能 ...
随机推荐
- Ubuntu 14.04 Intel 处理器 硬编解码配置(Intel® Media Server Studio)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- 常用命令rsyncscp-1
常用命令:rsync/scp scp scp命令文件传输 scp命令用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的.可能 ...
- 04.Android之动画问题
目录介绍 4.0.0.1 Android中有哪几种类型的动画,属性动画和补间动画有何区别?补间动画和属性动画常用的有哪些? 4.0.0.2 View动画为何不能真正改变View的位置?而属性动画为何可 ...
- 深入浅出Java多线程(十三):阻塞队列
引言 大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第十三篇内容:阻塞队列.大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!! 在多线程编程的世界里,生产者-消费者问 ...
- ES6常用数组方法及模拟实现
这里给大家分享下我搜索到的几个ES6常用数组方法及模拟实现,废话不多说,上代码 Array.from 可以将一个类数组转换成数组 在出现Array.from这个方法之前,我们转换类数组的方法: Arr ...
- dbvisivuser连oracle数据库报错没有权限
原因:数据库从11g升级为19c了 解决:ojdbc.jar也要换成最新的,导致报错的旧jar包2M大小,换成新jar包3M大小.替换jar包要将 dbvisivuser的tool driverMan ...
- 一个基于.NET Core构建的简单、跨平台、模块化的商城系统
前言 今天大姚给大家分享一个基于.NET Core构建的简单.跨平台.模块化.完全开源免费(MIT License)的商城系统:Module Shop. 商城后台管理端功能 商品:分类.品牌.单位.选 ...
- bram_to_vid
Entity: bram_to_vid File: bram_to_vid.v Diagram Description Company: Fpga Publish Engineer: FP Revis ...
- Spring Cloud Alibaba服务的注册与发现之Nacos部署
1.Nacos官网介绍 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理.Nacos 帮助您更敏捷和容 ...
- PostgreSQL 函数稳定性在索引与全表访问下的性能差异
一.构建测试数据 create or replace function test_volatile(id integer) returns bigint volatile language sql a ...