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. DHCPv6协议

    DHCPv6协议     1. 定义 IPv6 动态主机配置协议DHCPv6(Dynamic Host Configuration Protocol for IPv6)是针对IPv6编址方案设计,为主 ...

  2. linux 文件权限除了r、w、x外还有s、t、i、a权限说明

    linux 文件权限除了r.w.x外还有s.t.i.a权限 s: 文件属主和组设置SUID和GUID,文件在被设置了s权限后将以root身份执行.在设置s权限时文件属主.属组必须先设置相应的x权限,否 ...

  3. myeclipse非正常关闭处理办法

    myeclipse正常或非正常关闭后,再次运行,不显示启动时的logo和读条,进入主页面后程序基本就卡死,无法正常运行,解决办法. 方法一:修改工作空间在刚启动Myeclipse的时候会有一个选择工作 ...

  4. redis常用

    redis的key和string类型value限制均为512MB

  5. JS——封闭函数、闭包、内置对象

    封闭函数:时javascript中匿名函数的另一种写法,创建一个一开始就执行而不用命名的函数 示例: 1) (function(){ var str = '欢迎访问我的主页'; alert(str); ...

  6. D. Vitya and Strange Lesson

    http://codeforces.com/contest/842/problem/D 1.整体的数组是不用变的,比如数组a[]经过一次询问x后,然后再询问y,相当于询问x ^ y ^ a[i]后的m ...

  7. Java集合——集合框架Iterator接口

    1.集合输出 很多情况下我们需要把集合的内容进行输出,也就是遍历集合. 遍历集合的方式有以下几种: 1.Iterator 2.ListIterator 3.Enumeration(枚举方式,比较老一般 ...

  8. Have启动报错:java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient

    错误日志如下: [hadoop@master hive1.0.0]$ bin/hive Logging initialized using configuration in file:/opt/mod ...

  9. python接口自动化(四十一)- 发xml格式参数的post请求(超详解)

    简介 最近在工作中,遇到一种奇葩的接口,它的参数数据是通过xml,进行传递的,不要大惊小怪的,林子大了什么鸟都有,每个人的思路想法不一样,开发的接口也是各式各样的,如果想要统一的话,必须是提前团队已经 ...

  10. [知乎作答]·关于在Keras中多标签分类器训练准确率问题

    [知乎作答]·关于在Keras中多标签分类器训练准确率问题 本文来自知乎问题 关于在CNN中文本预测sigmoid分类器训练准确率的问题?中笔者的作答,来作为Keras中多标签分类器的使用解析教程. ...