原生js深入理解系列(四)--- 多个实例深入理解js的深拷贝和浅拷贝,多种方法实现对象的深拷贝
亲们为什么要研究深拷贝和浅拷贝呢,因为我们项目开发中有许多情况需要拷贝一个数组抑或是对象,但是单纯的靠=“赋值”并不会解决所有问题,如果遇到引用类型的对象改变新赋值的对象会造成原始对象也发生同样改变,而要去除影响就必须用到浅拷贝、深拷贝,深拷贝,对于引用对象需要进行深拷贝才会去除影响。如果是值类型直接“=”就好。
简而言之:
赋值:就是两个对象指向的内存地址一样,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))为深拷贝。
欢迎转载,转载请注明出处。欢迎大家交流学习

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
vue 组件高级用法实例详解
一、递归组件 组件在它的模板内可以递归地调用自己, 只要给组件设置name 的选项就可以了。 示例如下: <div id="app19"> <my-component19 :count="1"></my-component19> </div> Vue.component('my-component19',{ name: 'my-component19', //其实当你利用 Vue.component 全局注册了一个组件,全局的ID会被自动设置为组件的name。 props: { count: { type: Number, default: 1 } }, template: '<div><my-component19 :count="count+1" v-if="count<3"></my-component19></div>' }); var app19 = new Vue({ el: '#app19' }); //前端全栈学习交流圈:866109386 //面向1-3经验年前端开发...
- 下一篇
Spring Cloud Stream如何处理消息重复消费?
最近收到好几个类似的问题:使用Spring Cloud Stream操作RabbitMQ或Kafka的时候,出现消息重复消费的问题。通过沟通与排查下来主要还是用户对消费组的认识不够。其实,在之前的博文以及《Spring Cloud微服务实战》一书中都有提到关于消费组的概念以及作用。 那么什么是消费组呢?为什么要用消费组?它解决什么问题呢?摘录一段之前博文的内容,来解答这些疑问: 通常在生产环境,我们的每个服务都不会以单节点的方式运行在生产环境,当同一个服务启动多个实例的时候,这些实例都会绑定到同一个消息通道的目标主题(Topic)上。默认情况下,当生产者发出一条消息到绑定通道上,这条消息会产生多个副本被每个消费者实例接收和处理(出现上述重复消费问题)。但是有些业务场景之下,我们希望生产者产生的消息只被其中一个实例消费,这个时候我们需要为这些消费者设置消费组来实现这样的功能。 详细也可查看原文:消息驱动的微服务(消费组)。 下面,通过一个例子来看看如何使用消费组: 问题重现 构建消息消费端 第一步:创建绑定接口,绑定example-topic输入通道(默认情况下,会绑定到RabbitMQ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- Linux系统CentOS6、CentOS7手动修改IP地址
- 2048小游戏-低调大师作品
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案