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. idea dao使用@Mapper注解 业务类使用@Autowired 注入dao 爆红问题

    实际项目跑起来无影响,但是看起来不太爽. 可以在dao类添加org.springframework.stereotype.Repository 注解 或者可以在service类中使用 javax.an ...

  2. POJ1045 Bode Plot

    题目来源:http://poj.org/problem?id=1045 题目大意: 如图所示的交流电路,假设电路处于稳定状态,Vs为电源电压,w是频率,单位为弧度每秒,t表示时间. 则:V1 = Vs ...

  3. 微信小程序在sublime开发代码高亮显示

    问题:xxx.wxml 和xxx.wxss在subline中不高亮不显示 如下图,开发起来非常的不方便 解决办法:右下角将Plain Text改为Html,问题解决,可高亮,提高代码可读性,可提示,提 ...

  4. 2017 ACM/ICPC Asia Regional Shenyang Online card card card

    题意:看后面也应该知道是什么意思了 解法: 我们设置l,r,符合条件就是l=起始点,r=当前点,不符合l=i+1 学习了一下FASTIO #include <iostream> #incl ...

  5. Chapter10

    package scala import java.io.{PrintStream, PrintWriter}import java.util.Date import scala.util.loggi ...

  6. maven 引入本地项目jar报红线错误解决方法

    问题:本地创建了2个项目,A和B,A引入B,A的pom如下: <dependency> <groupId>com.ebc</groupId> <artifac ...

  7. 牛客网Java刷题知识点之插入排序(直接插入排序和希尔排序)、选择排序(直接选择排序和堆排序)、冒泡排序、快速排序、归并排序和基数排序(博主推荐)

    不多说,直接上干货! 插入排序包括直接插入排序.希尔排序. 1.直接插入排序: 如何写成代码: 首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用 ...

  8. Oracle11G的用户解锁、卸载以及基础操作

    Oracle用户解锁 [以下操作,必须以超级管理员身份登录,才能修改]oracle安装后,会默认生成很多个用户 以超级管理员身份登录,请注意,其中的空格符:[ sys是一个超级管理员,有最大的权限,d ...

  9. 浅谈堆-Heap(一)

    应用场景和前置知识复习 堆排序 排序我们都很熟悉,如冒泡排序.选择排序.希尔排序.归并排序.快速排序等,其实堆也可以用来排序,严格来说这里所说的堆是一种数据结构,排序只是它的应用场景之一 Top N的 ...

  10. 扔掉360:Linux下无线网卡作WiFi路由器(转薄荷开源网)

    这个话题很多人感兴趣,毕竟现在是无线互联时代.手机一族到外面去,首先关心的就是有没有 WiFi.Windows 7 用户可以安装 360 的软件,把笔记本电脑配置成路由器,供手机或其他电脑上网. 在 ...