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. 如何使用JMETER从JSON响应中提取数据

    如果你在这里,可能是因为你需要使用JMeter从Json响应中提取变量. 好消息!您正在掌握掌握JMeter Json Extractor的权威指南.作为Rest API测试指南的补充,您将学习掌握J ...

  2. BestCoder Round #86 1002

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=5805 题意:删除数列中一个数,找出相邻之差绝对值最大,求依次删除最大值的和 解法:删除边缘位置的数字需要注 ...

  3. Testlink安装后配置修改

    1.1. config.inc.php 1.1.1. 日志路径配置 /** *  @var string Path to store logs - *for security reasons (see ...

  4. Windows开机自动登录及取消自动登录的设置

    开机自动登录 1.开始菜单搜索框输入 “netplwiz” 按回车 或“Win+R”组合键打开“运行”框内输入“netplwiz” 或“运行”框内输入“control userpasswords2”( ...

  5. VS文档自动生成

    VS2008文档自动生成 (发现,Sandcastle主要是用于C#项目.里面的注释都是XML格式的.不太适合VC的.最终还是得用Doxygen) 一.Sandcastle简介: Sandcastle ...

  6. Maven的学习资料收集--(一)环境搭建

    这几天在做项目的时候用到了maven,但是自己没有从来没有接触过,所以咋网上找资料,终于找到了一下的资料,这个是别人总结的,我只是转载过来积累.请尊重原创. 官网地址:http://maven.apa ...

  7. quartz任务调度初次使用记录

    近期公司开发的数据交换系统嵌入了quartz任务调度功能,大概了解了任务调度的整个流程,项目中需要用到它来进行定时任务操作,对数据定时检查以及及时交换. Quartz是OpenSymphony开源组织 ...

  8. Docker:安装部署RabbitMQ

    前言 今天原本想讲解SpringBoot集成RabbitMQ的,临近开始写时才发现家里的电脑根本没有安装RabbitMQ呀.这下只好利用已有的阿里云服务器,直接Docker安装一下了,顺道记录下,算是 ...

  9. Regexp:常用的几个正则表达式

    1.isEmail /** * * @desc 判断是否为邮箱地址 * @param {String} str * @return {Boolean} */ function isEmail(str) ...

  10. Centos_linux系统的区别及实际查看

    在Linux系统查看系统版本为 32 位还是 64 位 [root@localhost ~]# cat /etc/redhat-release CentOS release 6.8 (Final) [ ...