从两种数据类型说起

在js中,变量的类型可以大致分成两种:基本数据类型和引用数据类型,其中基本数据类型指的是简单的数据段,包括:

  • Undefined
  • Null
  • Boolean
  • Number
  • String(字符串在一些其他语言中是被当做对象使用的,属于引用类型,但在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对应相等,最后的color1color2是相互独立的了,满足了我们的需要。当然对于对象类型也是一样的,使用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函数,从而完成深复制。

附注

对于浅复制,其实还有其他的实现方式,比如数组中concatslice方法,对于这些还是希望大家自己了解,本本主要针对深浅复制的实现原理进行解析。

小结

对于深浅复制的区别,其实核心的关键点就是是只复制了第一属性还是完全复制了所有的属性

js深浅复制(拷贝)的更多相关文章

  1. 详谈OC(object-c)深浅复制/拷贝-什么情况下用retain和copy

    读前小提示:对于深浅复制有一个清楚的了解,对于学习oc的朋友来说,至关重要.那么首先,我们要明白深浅复制是如何定义的呢.这里为了便于朋友们理解,定义如下. 浅 复 制:在复制操作时,对于被复制的对象的 ...

  2. js深浅复制

    一.数组的深浅拷贝 <body> <script type="text/javascript"> var arr = ["One",&q ...

  3. OC-深浅复制

    [OC学习-26]对象的浅拷贝和深拷贝——关键在于属性是否可被拷贝 对象的拷贝分为浅拷贝和深拷贝, 浅拷贝就是只拷贝对象,但是属性不拷贝,拷贝出来的对象和原来的对象共用属性,即指向同一个属性地址. 深 ...

  4. Objective-c 深浅复制

    深浅复制的定义: 浅复制:在复制时,对于被复制对象的每一层都是指针复制. 深复制:在复制时,对于被复制的对象至少有一层是对象复制. 完全复制:在复制时,对于被复制对象的每一层都是完全复制. retai ...

  5. JS对象复制(深拷贝、浅拷贝)

    如何在 JS 中复制对象 在本文中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个方面,介绍多种 JS 中复制对象的方法. 在开始之前,有一些基础知识值得一提:Javas ...

  6. C#设计模式(6)——原型模式(Prototype Pattern) C# 深浅复制 MemberwiseClone

    C#设计模式(6)——原型模式(Prototype Pattern)   一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创 ...

  7. C# Json反序列化 C# 实现表单的自动化测试<通过程序控制一个网页> 验证码处理类:UnCodebase.cs + BauDuAi 读取验证码的值(并非好的解决方案) 大话设计模式:原型模式 C# 深浅复制 MemberwiseClone

    C# Json反序列化   Json反序列化有两种方式[本人],一种是生成实体的,方便处理大量数据,复杂度稍高,一种是用匿名类写,方便读取数据,较为简单. 使用了Newtonsoft.Json,可以自 ...

  8. 【IPHONE开发-OBJECTC入门学习】复制对象,深浅复制

    转自:http://blog.csdn.net/java886o/article/details/9046273 #import <Foundation/Foundation.h> int ...

  9. JavaScript的深浅复制

    JavaScript的深浅复制 为什么有深复制.浅复制? JavaScript中有两种数据类型,基本数据类型如undefined.null.boolean.number.string,另一类是Obje ...

  10. 【转】js实现复制到剪贴板功能,兼容所有浏览器

    两天前听了一个H5的分享,会议上有一句话,非常有感触:不是你不能,而是你对自己的要求太低.很简单的一句话,相信很多事情不是大家做不到,真的是对自己的要求太低,如果对自己要求多一点,那么你取得的进步可能 ...

随机推荐

  1. windows条件下安装linux双系统

    工具: U盘 + rufus(使用烧录进linux镜像) linux镜像 1.windows 管理-压缩卷出一块空闲的磁盘空间(不要使用) 重启电脑   启动项  U盘启动  linux就自动安装,选 ...

  2. WPF之命令

    目录 命令系统的基本元素 基本元素之间的关系 小试命令 WPF的命令库 命令参数 命令与Binding的结合 近观命令 ICommand接口与RoutedCommand 自定义Command 定义命令 ...

  3. ADS1299开发调试总结之寄存器使用说明简析

    一 前记 在生物生理信号测量领域,ads12xx系列是一个无法绕过去的存在.笔者最近几个项目围绕着动物生理信号测量来做.所以用ads12xx比较多一些. 中间遇到了一些问题,这里做一个总结吧. 二 寄 ...

  4. 基于BES2500芯片的低功耗蓝牙BLE游戏手柄解决方案源码解析

    一 往事    寒冬腊月,在一个寂静的天空飘着碎银雪花的夜晚.我接到这么一个电话:"朋友,能否帮忙开发一个游戏手柄的案子?我们遇到了一些问题,迟迟无法解决.",喔,这边我陷入了沉思 ...

  5. 记录--开发uniapp nvue App+微信小程序,我踩过的坑( 纯干货 )

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 最近接了个项目,采用uniapp的nvue开发安卓和ios端+小程序端,第一次开发nvue,对于css布局这块,还是踩了很多坑.以及一些u ...

  6. Java 实现压缩图片,视频,音频案例

    Java 实现压缩图片,视频,音频案例 在 Java 中,要实现视频压缩通常需要使用外部的库或工具,因为 Java 标准库本身并不提供直接的视频处理功能.以下是一些常用的方法和工具来压缩视频: FFm ...

  7. ArcMap的mxd文件没有数据、显示感叹号怎么办?

      本文介绍在ArcMap软件中,导入.mxd地图文档文件后图层出现感叹号.地图显示空白等情况的解决办法.   在ArcMap软件使用过程中,我们经常会需要将包含有多个图层的.mxd地图文档文件导入软 ...

  8. 【面试】将 95% 求职者拒之门外的BAT大数据面试题-附解题方法(文末有福利)

    写在前面 最近不少读者找我要大数据面试题,我整理了很久,筛选出这10道容易出错的大数据面试题,希望对大家有所帮助.题目与解答整理自互联网,感谢分享这些面经的技术大牛们! 题目概览 如何从大量的 URL ...

  9. 【非插件实现】wordpress网站页脚添加,网站总访问数/今日访客数

    1 /** 2 * 统计全站总访问量/今日总访问量/当前是第几个访客 3 * @return [type] [description] 4 */ 5 function wb_site_count_us ...

  10. 为 AI 而生的编程语言「GitHub 热点速览」

    Mojo 是一种面向 AI 开发者的新型编程语言.它致力于将 Python 的简洁语法和 C 语言的高性能相结合,以填补研究和生产应用之间的差距.Mojo 自去年 5 月发布后,终于又有动作了.最近, ...