您现在的位置是:首页 > 文章详情

原生js深入理解系列(四)--- 多个实例深入理解js的深拷贝和浅拷贝,多种方法实现对象的深拷贝

日期:2018-11-26点击:569

亲们为什么要研究深拷贝和浅拷贝呢,因为我们项目开发中有许多情况需要拷贝一个数组抑或是对象,但是单纯的靠=“赋值”并不会解决所有问题,如果遇到引用类型的对象改变新赋值的对象会造成原始对象也发生同样改变,而要去除影响就必须用到浅拷贝、深拷贝,深拷贝,对于引用对象需要进行深拷贝才会去除影响。如果是值类型直接“=”就好。

简而言之:

赋值:就是两个对象指向的内存地址一样,a=b赋值后的新对象也指向同一个存储地址所以b变化a跟随变化,

浅拷贝:拷贝对象的一级元素的地址,如果一级元素全部为值类型就会互不干扰,如果一级元素有引用类型,改变引用类型的里面的值,会改变原对象。

深拷贝:拷贝对象各级元素的存储地址。

1.引用类型  引用类型通常叫做类(class),也就是说,遇到引用值,所处理的就是对象。new 出来的对象都是引用对象

(1)值类型:String, 数值、布尔值、null、undefined。 对于值类型一个对象一个存储位置所以会互不干扰

(2)引用类型:对象、数组、函数。对于引用类型,a=b赋值后的新对象也指向同一个存储地址所以b变化a跟随变化,

下图如果a是{key:'1', value:'value',children:{key:'child'}}这样一个对象,a赋值给b的话,a,b的存储位相同,里面的值变化也相同。如果a浅拷贝给b,a的值类型的值会复制给b,而a和b指向的引用类型的存储位置会相同,a引用类型里的children.key的值变化会引起b的children.key的值变化。即指针指向位置相同,那么该位置里的值也相同

而要改变这种情况需要改变b的指向,使其指向b存储位置如下图

下面是各种实例对比:

第一模块-数组:

数组的赋值:

a =[1,2,3]; b=a; b.push('change'); console.log('a:'+a,'b:'+b) // 结果 VM529:1 a:1,2,3,change b:1,2,3,change, 数组元素值的变化会互相影响 

数组浅拷贝:// 浅拷贝,拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用

还有浅拷贝数组的话可以利用,splice() 和 slice() 这两个方法。他们的区别一个是splice可以改变数组本身,slice不能改变数组本身。

Array.from() 方法从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例。

数组深拷贝:// 深拷贝,遍历到到每一项都是值类型时可以直接赋值具体实现参考本文下面对象深拷贝。下面代码代码是简单的遍历数组没有进行处理数组里面嵌套数组和对象的情况。

 a =[1,2,3]; let b =[] a.forEach(val => b.push(val));b.push('change'); console.log('a:'+a,'b:'+b) // 结果VM167:5 a:1,2,3 b:1,2,3,change 

第二模块-对象:

1-1对象赋值的代码及结果

bb改变后原数组aa也跟随变化,根本原因就是像第一张线框图描述的一样

1-2对象对象浅拷贝的代码及结果代码及结果,使用es6的Object.assign()方法

浅拷贝bb不会影响aa,因为改变是是值类型,但是如果是改变引用类型的值呢?如下图

结果很显然,对于引用对象Object.assign()就不行了,有点鸡肋了。aa的children里面的key值随bb的改变而改变

1-3对象深拷贝使用JSON.parse(JSON.stringify(obj)),即对象序列化

给子对象children改变属性如下图所示,aa原对象是不变的。但是JSON.parse(JSON.stringify(obj))实现深拷贝会存在一些问题,比如序列化会将序列化的undefined丢失,序列化正则对象(RegExp)会返回{},obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null,对于序列化构造函数construct()会被丢失
 

1-4对象深拷贝使用es6的Object.keys(obj),Object.values(obj)分别获得键数组和值数组,再通过函数根据条件循环回调deal()得到深拷贝值:《推荐使用回调》,留下个小问题,看官们可以试着这个思路去完成数组和函数回调实现深拷贝。

可复制代码:

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};

function deal(obj, bb = {}) {
    let keyArr = Object.keys(obj);
    let valueArr = Object.values(obj);
    valueArr.forEach((val,index) => {
    console.log(Object.prototype.toString.call(val))
        if(Object.prototype.toString.call(val) === "[object Object]") {
             bb[keyArr[index]] = deal(val, bb[keyArr[index]])
        }else {
            bb[keyArr[index]] = val
        }
    })
    return bb
}
BB = {}
BB=deal(aa);
console.log(BB)
BB['add']='addStr';
BB['children']['change']='变'
console.log('aa:',aa,'========','BB:',BB)

截图代码和结果:

第三模块对于对象来说可复制的浅、深拷贝代码和结果事例如下:

实例一

aa = { key:'1', value:'a', children:{ key: '2', value: 'b' } }; bb=aa; bb['add']='addStr'; console.log('aa:',aa,'====','bb',bb) // 下面结果出现aa,bb都有add属性,使用等号无法实现对象的拷贝,这个同样适用于数组 

VM1098:9 aa: {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object ==== bb {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object

实例二

aa = { key:'1', value:'a', children:{ key: '2', value: 'b' } }; bb = {} bb=Object.assign({},aa); bb['add']='addStr'; console.log('aa:',aa,'====','bb',bb) // 下面结果出现aa没有add属性,bb有add属性,使用assign可以实现对象的第一级键值对的拷贝。 

VM1223:10 aa: {key: "1", value: "a", children: {…}}children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object ==== bb {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectconstructor: ƒ Object()hasOwnProperty: ƒ hasOwnProperty()isPrototypeOf: ƒ isPrototypeOf()propertyIsEnumerable: ƒ propertyIsEnumerable()toLocaleString: ƒ toLocaleString()toString: ƒ toString()valueOf: ƒ valueOf()defineGetter: ƒ defineGetter()defineSetter: ƒ defineSetter()lookupGetter: ƒ lookupGetter()lookupSetter: ƒ lookupSetter()get proto: ƒ proto()set proto: ƒ proto()key: "1"value: "a"proto: Object

对于改变引用对象源码

实例如下,浅拷贝改变引用对象里面的值事,原对象也会改变

aa = { key:'1', value:'a', children:{ key: '2', value: 'b' } }; bb = {} bb=Object.assign({},aa); bb['children']['key']='addStr'; console.log('aa:',aa,'====','bb',bb) // ,结果如下: // 下面结果出现aa,bb的引用属性children里面的key属性都变成了addStr,使用assign不可以实现对象里引用属性的拷贝,因此assign()为浅拷贝。因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。 

在这里插入图片描述

实例三

aa = { key:'1', value:'a', children:{ key: '2', value: 'b' } }; bb = {} bb=JSON.parse(JSON.stringify(aa)); bb['add']='addStr'; bb.children.key='key'; console.log('aa:',aa,'====','bb',bb) // 下面结果出现aa保持初始状态,bb变成修改后的状态,使用JSON.parse(JSON.stringify(aa))为深拷贝。 

欢迎转载,转载请注明出处。欢迎大家交流学习

原文链接:https://my.oschina.net/u/2542841/blog/2961340
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章