探究JS中对象的深拷贝和浅拷贝
深拷贝和浅拷贝的区别
在讲深拷贝和浅拷贝的区别之前,回想一下我们平时拷贝一个对象时是怎么操作的?是不是像这样?
var testObj1 = {a: 1, b:2}, testObj2=testObj1;
testObj1.a = 7;
console.log(testObj1); //{a: 7, b:2}
console.log(testObj2); //{a: 7, b:2}
发现问题了吗?当testObj1变化时,testObj2相应的属性跟着变化了。这就是属于浅拷贝了,而所谓的深拷贝就是要做到当testObj1变化时testObj2不会跟着变化,这就是浅拷贝与深拷贝的区别。至少在我知道基本类型和引用类型的区别之前我是不知道为什么会这样的,那什么是基本类型和引用类型呢?
基本类型和引用类型
首先通过一个简单的例子看看与上面的例子有什么区别:
var num1 = 1, num2 = num1;
num1 = 7;
console.log(num1); // 7
console.log(num2); // 1
很显然,这里的变量num2并没有因为num1的变化而变化。其实,这里的num1和num2就是一种基本类型,而上面的那两个对象变量则属于引用类型。
ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指那些保存堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
所以,按我的理解,基本类型就是直接存在内存里的一个具体点,相互之间是独立的,而引用类型存储的只是一个指向具体内存地址的指针,当两个对象相等赋值时,他们实际上是指向的同一个内存地址,所以,当一个变了,另一个跟着变也就不奇怪了。打个不恰当的比喻,基本类型之间就像独立的小超市,相互之间的门头都是不一样的,而连锁超市的话都是一样的,而且都会一起变化。
如何实现深拷贝
既然已经知道了深拷贝和浅拷贝的区别以及为什么会出现这种区别,那怎么实现深拷贝呢?我之前在业务开发中有遇到过这个问题,但都是采用先新建一个空对象,再遍历目标对象的属性,将目标对象的属性值一个个赋值给这个新的空对象,从而得到了一个新的对象,这个对象是完全等于之前的对象的,但是不会受它变化而影响,所以算是初步实现了深拷贝的,大体实现如下:
var testObj3 = {a: 1, b:2, c:3, d:4};
var testObj4 = {};
for(var key in testObj3){
testObj4[key] = testObj3[key];
}
testObj3.a = 7;
console.log(testObj3); //{a: 7, b:2, c:3, d:4}
console.log(testObj4); //{a: 1, b:2, c:3, d:4}
咋一看这个方法当时虽然满足了我当时的业务需求,可是还有什么可以改进的地方呢?还有别的实现方式吗?
还有什么别的实现方式吗?
之前我也没细想过这个问题,知道后来偶尔间看到这篇文章,这篇文章提供的其他方式是我之前不知道,算是补上了我之前知识的盲区,在这里便是感谢。总结来说,还有以下几种方式:
- 1.通过Onject.assign()
- Object.assign是ES6中引入的一种用于合并对象的方法,具体使用方法可以看文档
- 这个方法我之前是用的不多的,但最近几个项目中有些使用后算是知道了这个属性,其实当时就应该可以想到可以用于深拷贝的,大体用法如下:
var obj1 = {x: 1, y:2}, obj2 = Object.assign({}, obj1);
obj2.x = 7;
console.log(obj1); //{a: 7, b:2}
console.log(obj2); //{a: 1, b:2}
- 可以看到,完美地深拷贝了这个对象,新对象并没有受原对象影响,可是,当对象为嵌套对象的时候呢?
var obj3 = {x:1, y:2, z:{name: 3}}, obj4 = Object.assign({}, obj3);
obj4.z.name = 7;
console.log(obj3); //{x:1, y:2, z:{name: 7}}
console.log(obj4); //{x:1, y:2, z:{name: 7}}
- 事实证明,对于嵌套对象,Object.assign()深拷贝失效了,所以说,Object.assign()只能实现一维对象的深拷贝
- 2.通过JSON.parse(JSON.stringify(obj))
- 这个方法也是在前一段时间朋友问我深拷贝的方法时偶尔查到的,之前是没用的,感觉还蛮好用的:
var obj5 = {x: 1, y:2}, obj6 = JSON.parse(JSON.stringify(obj5));
obj6.x = 7;
console.log(obj5); //{x: 1, y:2}
console.log(obj6); //{x: 7, y:2}
- 可是这个方法能解决多维对象的深拷贝问题吗?
var obj9 = {x:1, y:2, z:{name: 3}}, obj10 = Object.assign({}, obj3);
obj10.z.name = 7;
console.log(obj9); //{x:1, y:2, z:{name: 3}}
console.log(obj10); //{x:1, y:2, z:{name: 7}}
- 从代码上来看,这个方法完美地实现了多维对象的深拷贝,可是这样小小改动一下就会发现有问题了:
var obj9 = {x:1, y:2, z:{name: 3, func: function(){}}}, obj10 = Object.assign({}, obj3);
obj10.z.name = 7;
console.log(obj9); //{x:1, y:2, z:{name: 3, func: function(){}}}}
console.log(obj10); //{x:1, y:2, z:{name: 7}}
- 是的,当多维对象里有函数时,并没有复制,这是为什么,这个方法的MDN上有解释:
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
- 所以,JSON.parse(JSON.stringify(obj))这种方法虽然可以拷贝多维对象,但不能深拷贝含有undefined、function、symbol值的对象。
可以改进的地方
- 最开始那种通过for.in遍历所有属性的方式是会将原型链上的属性也遍历出来的,一般来说是不要的,所以最好使用obj.hasOwnProperty(key)来进行筛选判断,当然具体情况要根据业务需求来。
- 后面的几种方法都没有能完美地实现多维数组的深拷贝,所以还得祭出大杀器:递归。简单来说,就是先新建一个新对象,然后通过Object.keys()遍历原对象的所有属性列表,再遍历这个列表,如果有子集也是对象再递归一次,最终得到了深拷贝的对象:
function deepCopy(obj) {
let result = {};
let keys = Object.keys(obj), key=null, tem=null;
for(var i=0; i<keys.length; i++) {
key = keys[i];
temp = obj[key];
if (temp && typeof temp === 'object') {
result[key] = deepCopy(temp);
}
else{
result[key] = temp;
}
}
return result
}
console.log(deepCopy({x:1, y:3}))
var obj7 = {x:1, y:3, z:{name: 7, func: function(){}}};
var obj8 = deepCopy(obj7);
obj8.z.name = 8;
console.log(obj8); // {x:1, y:3, z:{name: 8, func: function(){}}}
console.log(obj7) // {x:1, y:3, z:{name: 7, func: function(){}}}
这样,就得到了一个可以兼容多维对象,并且可以实现对function,null,symbol值等特殊值的深拷贝。同时,我们也可以利用第三方库实现深拷贝,如jquery的$.extend和lodash的_.cloneDeep。
针对一种特殊情况的处理
本来上面的代码已经几乎完美地实现了深拷贝,但是,有一种特殊情况,就是当拷贝的对象本来就是一个循环引用的对象时,再去递归,那就无穷无尽会爆栈了。像这样的:
var obj1 = {
x: 1,
y: 2
};
obj1.z = obj1;
var obj2 = deepCopy(obj1);
我参考的处理方案就是加一层股票率判断,虽然我觉得这种情况根本就没有什么现实存在的意义,但就当一种临界值的处理吧:
function deepCopy(obj, parent) {
let result = {};
let keys = Object.keys(obj), key=null, tem=null, parent_=parent;
if (parent_) {
if (parent_.originalParent === obj) {
return parent_.curentParent;
}
parent_ = parent_.parent;
}
for(var i=0; i<keys.length; i++) {
key = keys[i];
temp = obj[key];
if (temp && typeof temp === 'object') {
result[key] = deepCopy(temp, {
originalParent: obj,
curentParent: result,
parent: parent
});
}
else{
result[key] = temp;
}
}
return result
}
obj1.z = obj1;
var obj2 = deepCopy(obj1);
console.log(obj2); //很多东西
这样,一个深拷贝函数就完成了,对于数组的话是同样适用的,毕竟数组也是特殊的对象嘛,当然实际项目中使用第三方库可能还会更加方便一点,很多时候造轮子并不是为了在项目中使用而是为了了解轮子的构造,这个很重要,之后还会继续造轮子的,这是个深入过膝的过程,加油!
参考文章:
探究JS中对象的深拷贝和浅拷贝的更多相关文章
- 谈谈java中对象的深拷贝与浅拷贝
知识点:java中关于Object.clone方法,对象的深拷贝与浅拷贝 引言: 在一些场景中,我们需要获取到一个对象的拷贝,这时候就可以用java中的Object.clone方法进行对象的复制,得到 ...
- js 中引用类型 的深拷贝 和 浅拷贝的区别
一.曾经在读JQ源码的时候,对深拷贝算是有了一点的理解.我们在项目中是不是经常会遇到这样的问题呢? 后台返回一个数组对象(引用类型).次数在页面渲染中需要对部分数据进行处理 比如:银行卡6234509 ...
- 在js中如何区分深拷贝与浅拷贝?
一.自我理解 简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层. 在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改. 在深拷贝中,原对象与新对象不共享相同的属 ...
- PHP中对象的深拷贝与浅拷贝
先说一下深拷贝和浅拷贝通俗理解 深拷贝:赋值时值完全复制,完全的copy,对其中一个作出改变,不会影响另一个 浅拷贝:赋值时,引用赋值,相当于取了一个别名.对其中一个修改,会影响另一个 PHP中, = ...
- 【转载】js中对象的使用
原文链接:http://www.jb51.net/article/90256.htm[侵删] 简单记录javascript中对象的使用 一.创建对象 //创建一个空对象 var o={}; //创建一 ...
- java 复制Map对象(深拷贝与浅拷贝)
java 复制Map对象(深拷贝与浅拷贝) CreationTime--2018年6月4日10点00分 Author:Marydon 1.深拷贝与浅拷贝 浅拷贝:只复制对象的引用,两个引用仍然指向 ...
- [转]JS中对象与字符串的互相转换
原文地址:http://www.cnblogs.com/luminji/p/3617160.html 在使用 JSON2.JS 文件的 JSON.parse(data) 方法时候,碰到了问题: thr ...
- JS中对象与字符串的互相转换
在使用 JSON2.JS 文件的 JSON.parse(data) 方法时候,碰到了问题: throw new SyntaxError('JSON.parse'); 查询资料,大概意思如下: JSON ...
- js中对象的一些特性,JSON,scroll家族
一.js中对象的一些特性 对象的动态特性 1.当对象有这个属性时,会对属性的值重写 2.当对象没有这个属性时,会为对象创建一个新属性,并赋值 获得对象的属性的方式 为元素设置DOM0级事件 二.JSO ...
随机推荐
- nginx 上php不可写解决方法
在php.ini中设置的session.save_path会被php-fpm.conf中覆盖 打开php-fpm.conf文件找到php_value['session.save_apth'] 这里的/ ...
- JAVA参数没有引用传递,只有值传递
原文章地址:http://www.cnblogs.com/clara/archive/2011/09/17/2179493.html 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性, ...
- Python开发【模块】:Requests(一)
Requests模块 1.模块说明 Requests 是使用 Apache2 Licensed 许可证的 HTTP 库.用 Python 编写,真正的为人类着想. Python 标准库中的 urlli ...
- 点击劫持漏洞解决( Clickjacking: X-Frame-Options header missing)
点击劫持漏洞 X-Frame-Options HTTP 响应头, 可以指示浏览器是否应该加载一个 iframe 中的页面. 网站可以通过设置 X-Frame-Options 阻止站点内的页面被其他页面 ...
- Nginx rewrite 中break与last指令的区别
location /break/ { rewrite ^/break/(.*) /test/$1 break; return 402; } location /last/ { rewrite ^/la ...
- Mybatis三剑客之mybatis-plugin
搜索mybatis plugin并安装. 如果没有的话,就按照如下: 1. 简介 mybatis plugin作为一款优秀的mybatis跳转插件,比起free mybatis plugin插 ...
- 模仿Masonary写一个计算器
1.CaculatorMaker @interface CaculatorMaker : NSObject @property(nonatomic,assign)int result; -(Cacul ...
- Linux下Rsync+Inotify-tools实现数据实时同步
Linux下Rsync+Inotify-tools实现数据实时同步 注意:下面的三个案例都是rsync 每次都是全量的同步(这就坑爹了),而且 file列表是循环形式触发rsync ,等于有10个文件 ...
- MegaCli 监控raid状态
MegaCli 监控raid状态 http://blog.chinaunix.net/uid-25135004-id-3139293.html 简介 MegaCli是一款管理维护硬件RAID软件,可以 ...
- Linux光标移动异常
刚刚安装完毕CentOS7.5,进行基础优化来着,发现我的光标具有如下神奇的故障. 无法移动到头部? 刚开始还以为是ISO镜像的问题,后校验了阿里云官网镜像的MD5值,和本地镜像MD5对比之后, 发现 ...