理解深拷贝和浅拷贝之前,先来看一下JavaScript的数据类型。

1、基本类型和引用类型

//案例1
var num1 = 1, num2 = num1;
console.log(num1) //
console.log(num2) //
num2 = 2; //修改num2
console.log(num1) //
console.log(num2) //
//案例2
var obj1 = {x: 1, y: 2}, obj2 = obj1;
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}
obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 2, y: 2}
console.log(obj2) //{x: 2, y: 2}

ECMAScript 变量可能包含两种不同数据类型的值: 基本类型值 和 引用类型值。

基本类型值: 那些保存在栈内存中的简单数据段; 即这些值完全保存在内存中的一个位置。

引用类型值: 那些保存在堆内存中的对象;也就是说变量中保存的实际上是一个指针,这个指针指向内存中的另一个位置(保存对象)。

基本类型赋值等于在一个新的地方安装连锁店的规范标准新开一个分店,新开的店与其他旧店互不相关,各自运营;

而引用类型赋值相当于一个店有两把钥匙,交给两个老板同时管理,两个老板的行为都有可能对一间店的运营造成影响。

常见的基本数据类型: String、Number、Boolean、Null、Undefined、Symbol

常见的引用数据类型:Object、 Array、Function

2、深拷贝 与 浅拷贝

深拷贝 与 浅拷贝的概念只存在于 引用类型。

  • 浅拷贝:只能实现一维对象的复制;当对象是多维时(例如:二维数组或对象),改变二维数组的值会影响原数组【因为二维数组值复制的是其引用】。
  • 深拷贝:可以实现多维对象的复制。

(1)Array 和 Object 自身所带的深拷贝的方法

Array中具备深拷贝的方法: splice、concat、Array.from() ---  但是只能实现一维数组的深拷贝。

var arr1 = [1, 2], arr2 = arr1.slice();
console.log(arr1); //[1, 2]
console.log(arr2); //[1, 2]
arr2[0] = 3; //修改arr2
console.log(arr1); //[1, 2]
console.log(arr2); //[3, 2]

二维数组的情况下,arr2的改变也会同时改变arr1:

var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice();
console.log(arr1); //[1, 2, [3, 4]]
console.log(arr2); //[1, 2, [3, 4]]
arr2[2][1] = 5;
console.log(arr1); //[1, 2, [3, 5]]
console.log(arr2); //[1, 2, [3, 5]]

Object.assign()也只能实现一维对象的深拷贝,并不能实现真正的深拷贝。

var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}
obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 2, y: 2} //二维对象
var obj1 = {
x: 1,
y: {  
m: 1
}
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 2}}
console.log(obj2) //{x: 1, y: {m: 2}}

造成只能实现一维对象深拷贝的原因:第一层的属性确实实现了深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址。


(2)JSON.parse(JSON.stringify(obj)) ,可是实现二维对象的深拷贝:

var obj1 = {
x: 1,
y: {
m: 1
}
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 2, y: {m: 2}}

JSON.parse(JSON.stringify(obj)) 进行深拷贝是有局限性的;不能深拷贝还有 undefined、function、symbol值的对象。

var obj1 = {
x: 1,
y: undefined,
z: function add(z1, z2) {
return z1 + z2
},
a: Symbol("foo")
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(JSON.stringify(obj1)); //{"x":1}
console.log(obj2) //{x: 1}

 MDN文档中提示:undefined、任意的函数以及 symbol 值,

在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。

(3)利用递归 来彻底解决深拷贝的问题

function deepCopy(obj) {
// 创建一个新对象
let result = {}
let keys = Object.keys(obj),
key = null,
temp = null;
for (let 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;
}
var obj1 = {
x: {
m: 1
},
y: undefined,
z: function add(z1, z2) {
return z1 + z2
},
a: Symbol("foo")
};
var obj2 = deepCopy(obj1);
obj2.x.m = 2;
console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}

(4)非常特殊的情况---- 循环引用拷贝

var obj1 = {
x: 1,
y: 2
};
obj1.z = obj1;
var obj2 = deepCopy(obj1);

此时如果调用刚才的deepCopy函数的话,会陷入一个循环的递归过程,从而导致爆栈。jquery的$.extend也没有解决。

解决这个问题: 判断一个对象的字段是否引用了这个对象 或  这个对象的任意父类即可。

function deepCopy(obj, parent = null) {
// 创建一个新对象
let result = {};
let keys = Object.keys(obj),
key = null,
temp= null,
_parent = parent;
// 该字段有父级则需要追溯该字段的父级
while (_parent) {
// 如果该字段引用了它的父级则为循环引用
if (_parent.originalParent === obj) {
// 循环引用直接返回同级的新对象
return _parent.currentParent;
}
_parent = _parent.parent;
}
for (let i = 0; i < keys.length; i++) {
key = keys[i];
temp= obj[key];
// 如果字段的值也是一个对象
if (temp && typeof temp=== 'object') {
// 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用
result[key] = DeepCopy(temp, {
originalParent: obj,
currentParent: result,
parent: parent
});
} else {
result[key] = temp;
}
}
return result;
}
var obj1 = {
x: 1,
y: 2
};
obj1.z = obj1;
var obj2 = deepCopy(obj1);
console.log(obj1); //太长了去浏览器试一下吧~
console.log(obj2); //太长了去浏览器试一下吧~

(5)当然也可以使用第三方库

   jquery的$.extend 和 lodash的_.cloneDeep 来解决深拷贝的问题。

3、总结

  • 简单的一维层次的拷贝可以利用数组自身方法和对象的Object.assign实现,在二维层次上方法失效,无法实现深拷贝
  • 简单粗暴的常见的拷贝可以通过JSON.parse(JSON.stringify(obj))实现,但对于属性的某些特殊类型的值失效。
  • 终极方法,用递归实现引用类型的深拷贝
  • 当然还有其他方法,比如使用第三方库内封装的方法

JavaScript基础之--- 深拷贝与浅拷贝的更多相关文章

  1. javascript中的深拷贝与浅拷贝

    javascript中的深拷贝与浅拷贝 基础概念 在了解深拷贝与浅拷贝的时候需要先了解一些基础知识 核心知识点之 堆与栈 栈(stack)为自动分配的内存空间,它由系统自动释放: 堆(heap)则是动 ...

  2. JavaScript中的深拷贝和浅拷贝!【有错误】还未修改!请逛其他园子!

    JavaScript中的深拷贝和浅拷贝! 浅拷贝 1.浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用.{也就是拷贝的是地址!简而言之就是在新的对象中修改深层次的值也会影响原来的对象!} // 2.深 ...

  3. 深入剖析javaScript中的深拷贝和浅拷贝

    如何区分深拷贝与浅拷贝,简单来说,假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝:我们先看两个简单的案例: //案例1(深拷贝) var a ...

  4. Java基础(十三)--深拷贝和浅拷贝

    在上篇文章:Java基础(十二)--clone()方法,我们简单介绍了clone()的使用 clone()对于基本数据类型的拷贝是完全没问题的,但是如果是引用数据类型呢? @Data @NoArgsC ...

  5. 理解JavaScript中的深拷贝和浅拷贝

    , num2 = num1;console.log(num1) //1console.log(num2) //1num2 = 2; //修改num2console.log(num1) //1conso ...

  6. 低门槛彻底理解JavaScript中的深拷贝和浅拷贝

    作者 | 吴胜斌 来源 | https://www.simbawu.com/article/search/9 在说深拷贝与浅拷贝前,我们先看两个简单的案例: //案例1var num1 = 1, nu ...

  7. Javascript中的深拷贝和浅拷贝

    var obj = { a:1, arr: [1,2] }; var obj1 = obj; //浅复制 var obj2 = deepCopy(obj); //深复制 javascript中创建对象 ...

  8. 《JavaScript总结》深拷贝和浅拷贝

    在javascript中,数据主要分基本类型和引用类型两种. 基本类型的赋值比较简单,但是引用类型的赋值,会存在一些问题,那我们用代码来分析一下. 一.浅拷贝 var one = "测试1& ...

  9. 探究JS中对象的深拷贝和浅拷贝

    深拷贝和浅拷贝的区别 在讲深拷贝和浅拷贝的区别之前,回想一下我们平时拷贝一个对象时是怎么操作的?是不是像这样? var testObj1 = {a: 1, b:2}, testObj2=testObj ...

随机推荐

  1. Linux系统账户管理指令

    sudo passwd -l weblogic 锁定账户 sudo passwd -u weblogic 解锁账户 useradd weblogic -p xxxxx 添加账户指定密码 sudo us ...

  2. mysql orderby 问题

    开发写的sql select * from aaa where course_id=xx order by  a,b 当a,b条件都一致时,默认应该以id排序,当数据条数大于1x条(17)时,结果变为 ...

  3. (转) Windows下MySQL免安装版的下载与配置

    本人在尊重原著的前提下.针对在实践中所遇到的问题加以整理和完善,如有不足之处,还请各位大神指点江山O(∩_∩)O~ 主要是因为平时自己学习时候会用到.及免安装版本的方便.对于个人开发者挺实用的! 安装 ...

  4. 炸弹:线段树优化建边+tarjan缩点+建反边+跑拓扑

    这道题我做了有半个月了...终于A了... 有图为证 一句话题解:二分LR线段树优化建边+tarjan缩点+建反边+跑拓扑统计答案 首先我们根据题意,判断出来要炸弹可以连着炸,就是这个炸弹能炸到的可以 ...

  5. h5页面ios键盘弹出收起后页面底部留白问题

    <input placeholder="验证码" type="tel" v-model="verify" maxlength=&quo ...

  6. Rsync服务端部署流程

    Rsync服务端部署流程       Rsync服务端部署流程: 一.rsync服务端配置流程 配置rsync配置文件/etc/rsyncd.conf 创建同步的本地目录/dingjian 并根据需要 ...

  7. python内存相关以及深浅拷贝讲解

    3.9 内存相关 3.9.1 id,查看内存地址 >>> v1 = [11,22,33] >>> v2 = [11,22,33] >>> prin ...

  8. Python _easygui详细版

    1. msgbox msgbox(msg='(Your message goes here)', title=' ', ok_button='OK', image=None, root=None) m ...

  9. php将base64字符串转换为图片

    昨天用一个js插件 [链接]: http://www.erdangjiade.com/js/910.html 实行了图片裁剪并预览,不过它生产的图片资源是一个base64字符串,不好保存后来在网上找到 ...

  10. Min_25筛初级应用:求$[1,n]$内质数个数

    代码 #include <bits/stdc++.h> #define rin(i,a,b) for(int i=(a);i<=(b);++i) #define irin(i,a,b ...