js的深拷贝浅拷贝是很常遇到的问题,一直模模糊糊有点说不过去,所以这次好好总结一下。

1、js的引用

  JS分为基础类型和引用类型两种数据类型:

  基础类型:number、string、boolean、null、undefined、symbol

引用类型:Object(Array,Date,RegExp,Function)

  它们有个区别 —— 保存位置不同。基本数据类型保存在栈内存中;引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址。所以应该记住:基础数据类型赋值时是【传值】,而引用数据类型赋值时是【传址】。

为什么基本数据类型保存在栈中,而引用数据类型保存在堆中?
1)堆比栈大,栈比堆速度快;
2)基本数据类型比较稳定,而且相对来说占用的内存小;
3)引用数据类型大小是动态的,而且是无限的,引用值的大小会改变,不能把它放在栈中,否则会降低变量查找的速度,因此放在变量栈空间的值是该对象存储在堆中的地址,地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响;
4)堆内存是无序存储,可以根据引用直接获取;

因此引用数据类型赋值、传参时新旧值会相互影响,如下:

1)引用类型赋值

var obj ={a:1,b:2,c:3};
var obj1= obj;
obj1.a=5;
console.log(obj); //{a: 5, b: 2, c: 3}
console.log(obj1); //{a: 5, b: 2, c: 3}

2)引用类型作为函数参数传递

var obj ={a:1,b:2,c:3};
fn=(obj)=>{
var obj2= obj;
obj2.a=5;
};
fn(obj);
console.log(obj);//{a: 5, b: 2, c: 3}

ps:如果引用赋值后将对象置空,则相互不受影响(因为对象公用一个内存,当内存销毁的时候,指向这个内存空间的所有指针需要重新定义,不然会造成野指针错误。)

var obj = {a:1,b:2,c:3};
var obj3 = obj;
obj = {};
obj.a = 5;
console.log(obj3);//{a: 1, b: 2, c: 3}
console.log(obj); //{a: 5}

2、浅拷贝与深拷贝

  浅拷贝:拷贝原始对象的第一层属性,当属性是基本类型时,拷贝属性的值,当属性是引用类型,拷贝属性的内存地址,因此新旧对象修改属性相互影响。

  深拷贝:拷贝原始对象的所有的属性,并拷贝属性指向的动态分配的内存,深拷贝新旧对象不共享内存,修改新对象不会改到原对象。

3、浅拷贝

1)解构赋值

var obj = {a:{i:1,j:2,k:3},b:2,c:3};
var {...obj1} = obj;
obj1.b=5;obj1.a.i=5;
console.log(obj);//{a:{i:1,j:2,k:3},b:2,c:3};
console.log(obj1);//{a:{i:5,j:2,k:3},b:5,c:3};
var arr=[1,[1,2,3],2,3];
var [...arr1]=arr;
arr1[0]=5;
arr1[1][0]=5;
console.log(arr); //[1,[5,2,3],2,3];
console.log(arr1); //[5,[5,2,3],2,3];

2)Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

var obj = {a:{i:1,j:2,k:3},b:2,c:3};
var obj2 = Object.assign({},obj);
obj2.b=5;
obj2.a.i=5;
console.log(obj); //{a:{i:5,j:2,k:3},b:2,c:3};
console.log(obj2); //{a:{i:5,j:2,k:3},b:5,c:3};

3)Array.prototype.slice() 方法提取并返回一个新的数组,如果源数组中的元素是个对象的引用,slice会拷贝这个对象的引用到新的数组。

let arr=[0,1,[2,3],4],
arr1=arr.slice();
arr[0]=1;
arr[2][0]=1;
console.log(arr); //[1,1,[1,3],4]
console.log(arr1); //[0,1,[1,3],4]

4)Array.prototype.concat() 用于合并多个数组,并返回一个新的数组。

var arr1 = [{a: 'old'}, 'b', 'c']
var arr2 = [{b: 'old'}, 'd', 'e']
var arr3 = arr1.concat(arr2)
arr3[0].a = 'new'
arr3[3].b = 'new'
console.log(arr3)//[{a: 'new'}, 'b', 'c',{b: 'new'}, 'd', 'e']
console.log(arr1[0].a) // new
console.log(arr2[0].b) // new

4、实现深拷贝

1)循环递归

function isObj(obj) {
return (typeof obj === 'object' || typeof obj === 'function') && obj !== null;
}
function deepCopy(obj) {
let tempObj = Array.isArray(obj) ? [] : {};
for(let key in obj) {
tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key];
}
return tempObj;
}

2)JSON方法

JSON.parse(JSON.stringify(obj));

3)另外还有jquery的$.extend() 和lodash的cloneDeep()

6、深拷贝的一些坑

1)环 

环就是对象循环引用,导致自己成为一个闭环,例如下面这个对象:

var a = {}
a.a = a

用之前的deepCopy试一下,会报栈溢出...

解决:使用一个WeakMap结构存储已经被拷贝的对象,每一次进行拷贝的时候就先向WeakMap查询该对象是否已经被拷贝,如果已经被拷贝则取出该对象并返回。

deepCopy函数改造成如下:

function deepCopy(obj, hash = new WeakMap()) {
if(hash.has(obj)) return hash.get(obj)
let cloneObj = Array.isArray(obj) ? [] : {}
hash.set(obj, cloneObj)
for (let key in obj) {
cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
}
return cloneObj
}

运行:

2)特殊对象 

我们定义一个对象

var obj = {
  arr: [111, 222],
  obj: {key: '对象'},
  fn: () => {console.log('函数')},
  date: new Date(),reg: /正则/ig,
  u:undefined,
  s:Symbol('symbol'),
};

用 之前写的deepCopy()方法拷贝一个新的对象:结果发现 date对象、函数和正则都成了空对象。

再用JSON方法试一次:结果发现date对象成了字符串,正则成了一个空对象, `undefined`、`symbol` 和函数直接就不见了。

解决:参考MDN上的结构化拷贝

function deepCopy(obj, hash = new WeakMap()) {
let cloneObj
let Constructor = obj.constructor
switch(Constructor){
case RegExp:
cloneObj = new Constructor(obj)
break
case Date:
cloneObj = new Constructor(obj.getTime())
break
case Function:
cloneObj = eval(obj)
     break
default:
if(hash.has(obj)) return hash.get(obj)
cloneObj = new Constructor()
hash.set(obj, cloneObj)
}
for (let key in obj) {
cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
}
return cloneObj
}

运行:

另外可见function类型使用eval(fn)好像是成功的复制了,但是当不是以箭头函数的方式写的时候就不行了...
暂时还没找到一个好方法复制function,悉听有办法的同志指教╰( ̄▽ ̄)╭

JS中的引用、浅拷贝和深拷贝的更多相关文章

  1. JS中如何进行对象的深拷贝

    在JS中,一般的=号传递的都是对象/数组的引用,并没有真正地拷贝一个对象,那如何进行对象的深度拷贝呢?如果你对此也有疑问,这篇文章或许能够帮助到你 一.对象引用.浅层拷贝与深层拷贝的区别 js的对象引 ...

  2. Python中赋值、浅拷贝与深拷贝

    python中关于对象复制有三种类型的使用方式,赋值.浅拷贝与深拷贝.他们既有区别又有联系,刚好最近碰到这一类的问题,研究下. 一.赋值 在python中,对象的赋值就是简单的对象引用,这点和C++不 ...

  3. python中赋值、浅拷贝、深拷贝详解(转)

    一.赋值 >>> a = [1, 2, 3]>>> b = a>>> print(id(a), id(b), sep='\n')139701469 ...

  4. 关于python中赋值、浅拷贝、深拷贝之间区别的深入分析

    当重新学习了计算机基础课程<数据结构和算法分析>后再来看这篇自己以前写的博文,发现错误百出.python内置数据类型之所以会有这些特性,归根结底是它采用的是传递内存地址的方式,而不是传递真 ...

  5. Python中赋值、浅拷贝和深拷贝的区别

    前言文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http: ...

  6. js 中数组或者对象的深拷贝和浅拷贝

    浅拷贝 : 就是两个js 对象指向同一块内存地址,所以当obj1 ,obj2指向obj3的时候,一旦其中一个改变,其他的便会改变! 深拷贝:就是重新复制一块内存,这样就不会互相影响. 有些时候我们定义 ...

  7. 图解python中赋值、浅拷贝、深拷贝的区别

    Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果.下面本文就通过简单的例子介绍一下这些概念之间的差别. 对象赋值 直接看一段代码: will = ...

  8. python中赋值和浅拷贝与深拷贝

    初学编程的小伙伴都会对于深浅拷贝的用法有些疑问,今天我们就结合python变量存储的特性从内存的角度来谈一谈赋值和深浅拷贝~~~ 预备知识一——python的变量及其存储 在详细的了解python中赋 ...

  9. js 中多维数组的深拷贝的多种实现方式

    因为javascript分原始类型与引用类型(与java.c#类似).Array是引用类型,所以直接用=号赋值的话,只是把源数组的地址(或叫指针)赋值给目的数组,并没有实现数组的数据的拷贝.另外对一维 ...

随机推荐

  1. Luogu P4478 [BJWC2018]上学路线 卢卡斯+组合+CRT

    首先,从$(0,0)$走到$(n,m)$的方案数是$ C_{n+m}^n$,可以把走的方向看作一种序列,这个序列长$ n+m$ ,你需要从中任取$n$个位置,让他向右走: 然后就是如何处理不能走的点: ...

  2. 树的计数 Prufer序列+Cayley公式

    先安利一发.让我秒懂.. 第一次讲这个是在寒假...然而当时秦神太巨了导致我这个蒟蒻自闭+颓废...早就忘了这个东西了... 结果今天老师留的题中有两道这种的:Luogu P4981 P4430 然后 ...

  3. C数据结构与算法-算法复杂度

    算法复杂度分为时间复杂度T(n)和空间复杂度F(n) 时间复杂度:也就是执行算法程序所需的时间,与硬件的速度.编程语言的级别.编译器的优化.数据的规模.执行的频度有关,前三个有很大的不确定性,所以衡量 ...

  4. Java文件与io——字节数组流数据流字符串流

    字节数组流 ByteArrayInputStream:包含一个内部缓冲区,该缓冲区包含从流中读取的字节.内部计数器跟踪read方法要提供的下一个字节.关闭ByteArrayInputStream无效. ...

  5. 牛客网Java刷题知识点之基本类型的自动转换和基本类型的强制转换

    不多说,直接上干货! TypeConvertDemo.java //自动类型转换 class TypeConvertDemo { public static void main(String[] ar ...

  6. YARN的架构及原理

    1. YARN产生背景 MapReduce本身存在着一些问题: 1)JobTracker单点故障问题:如果Hadoop集群的JobTracker挂掉,则整个分布式集群都不能使用了. 2)JobTrac ...

  7. pat1079. Total Sales of Supply Chain (25)

    1079. Total Sales of Supply Chain (25) 时间限制 250 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHE ...

  8. Docker for mac 安装 kong

    首先安装一个 PostgreSQL,选的版本是 9.5 $ docker run -d --name kong-database \ -p : \ -e "POSTGRES_USER=kon ...

  9. python.h没有那个文件或目录解决方法

    我用的是Deepin Linux,这应该是linux平台的问题,别的linux os也是执行安装,命令不同而已,windows和Mac不太清楚. 如果你使用的是python2.x,那么使用下面的语句: ...

  10. testNG测试基础一

    1.TestNG概念 TestNG:Testing Next Generation 下一代测试技术,是一套根据JUnit和Nunit思想构建的利用注释来强化测试功能的测试框架,可用来做单元测试,也可用 ...