深拷贝和浅拷贝的区别

在讲深拷贝和浅拷贝的区别之前,回想一下我们平时拷贝一个对象时是怎么操作的?是不是像这样?

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中对象的深拷贝和浅拷贝的更多相关文章

  1. 谈谈java中对象的深拷贝与浅拷贝

    知识点:java中关于Object.clone方法,对象的深拷贝与浅拷贝 引言: 在一些场景中,我们需要获取到一个对象的拷贝,这时候就可以用java中的Object.clone方法进行对象的复制,得到 ...

  2. js 中引用类型 的深拷贝 和 浅拷贝的区别

    一.曾经在读JQ源码的时候,对深拷贝算是有了一点的理解.我们在项目中是不是经常会遇到这样的问题呢? 后台返回一个数组对象(引用类型).次数在页面渲染中需要对部分数据进行处理 比如:银行卡6234509 ...

  3. 在js中如何区分深拷贝与浅拷贝?

    一.自我理解 简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层. 在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改. 在深拷贝中,原对象与新对象不共享相同的属 ...

  4. PHP中对象的深拷贝与浅拷贝

    先说一下深拷贝和浅拷贝通俗理解 深拷贝:赋值时值完全复制,完全的copy,对其中一个作出改变,不会影响另一个 浅拷贝:赋值时,引用赋值,相当于取了一个别名.对其中一个修改,会影响另一个 PHP中, = ...

  5. 【转载】js中对象的使用

    原文链接:http://www.jb51.net/article/90256.htm[侵删] 简单记录javascript中对象的使用 一.创建对象 //创建一个空对象 var o={}; //创建一 ...

  6. java 复制Map对象(深拷贝与浅拷贝)

      java 复制Map对象(深拷贝与浅拷贝) CreationTime--2018年6月4日10点00分 Author:Marydon 1.深拷贝与浅拷贝 浅拷贝:只复制对象的引用,两个引用仍然指向 ...

  7. [转]JS中对象与字符串的互相转换

    原文地址:http://www.cnblogs.com/luminji/p/3617160.html 在使用 JSON2.JS 文件的 JSON.parse(data) 方法时候,碰到了问题: thr ...

  8. JS中对象与字符串的互相转换

    在使用 JSON2.JS 文件的 JSON.parse(data) 方法时候,碰到了问题: throw new SyntaxError('JSON.parse'); 查询资料,大概意思如下: JSON ...

  9. js中对象的一些特性,JSON,scroll家族

    一.js中对象的一些特性 对象的动态特性 1.当对象有这个属性时,会对属性的值重写 2.当对象没有这个属性时,会为对象创建一个新属性,并赋值 获得对象的属性的方式 为元素设置DOM0级事件 二.JSO ...

随机推荐

  1. talib 中文文档(十二):Pattern Recognition Functions K线模式识别,形态识别

    Pattern Recognition Functions K线模式识别,形态识别 CDL2CROWS - Two Crows 函数名:CDL2CROWS 名称:Two Crows 两只乌鸦 简介:三 ...

  2. Optimal Milking---poj2112(多重匹配+Floyd+二分)

    题目链接:http://poj.org/problem?id=2112 题意:K个挤奶器(编号1~K),每个挤奶器每天最多供M头奶牛.共有C头奶牛(编号K+1~K+C).挤奶器和奶牛间有不同长度的路. ...

  3. Python开发【项目】:大型模拟战争游戏(外星人入侵)

    外星人入侵 游戏概述: 现在准备用python开始搞一个大型游戏,模拟未来战争,地球人狙击外星人大战(其实就是小蜜蜂游戏2333),玩家控制一个飞船,用子弹歼灭屏幕上空的外星飞船:项目用到了Pygam ...

  4. yii2框架2 (二)项目结构

    原文 http://www.yiichina.com/doc/guide/2.0/structure-overview 应用结构 应用中最重要的目录和文件(假设应用根目录是 basic): basic ...

  5. Design Pattern in Simple Examples

    Instead of defining what is design pattern lets define what we mean by design and what we mean by pa ...

  6. Java-idea-常用技巧-转maven,解决包依赖冲突

    1.Intellij IDEA如何将普通工程转换成maven工程 项目上右键 Add Framework Support,选择maven 2.Intellij IDEA 自动生成 serialVers ...

  7. 102-advanced-代码分割

    1.Bundling 大多数React应用程序将使用Webpack或Browserify等工具“捆绑”文件.捆绑是跟踪导入的文件并将它们合并到单个文件中的过程:“捆绑”.然后,该包可以包含在网页中以一 ...

  8. GlobeMapper生成Google瓦片

    GlobeMapper真牛,生成自己的瓦片叠在GoogleMap的地图上,效果如下:

  9. Jquery EasyUI插件

    属性 属性是定义在 jQuery.fn.{plugin}.defaults.比如,dialog 的属性是定义在 jQuery.fn.dialog.defaults. 事件 事件(回调函数)也是定义在 ...

  10. xml转为array

    PHP实现微信支付,微信支付宝返回的xml结果如下: <xml>   <appid><![CDATA[wx2421b1c4370ec43b]]></appid ...