深拷贝的缺点_拷贝?还傻傻分不清深浅?
「引言」
❝
臣闻求木之长者,必固其根本;欲流之远者,必浚其泉源。
---- 魏征 《谏太宗十思疏》
❞
或许你会问到,网上已经把深浅拷贝(算一个面试的高频考点了吧)的文章都快写烂了,为什么自己还要重新操刀写一遍呢!?
❝
首先,一些文章,讲不清也道不明本质;另外,确实有很优秀的人写的很是生动,让我直接看到了风景,却不知道沿途是不是也有自己错过的美景,唯有尝试过,才会真正成为自己的~
❞
首先,我们先来看一张笔者整理的脑图,梳理一下~
希望通过本文的总结,你会有以下几点收获:
- 什么是深浅拷贝?他们与赋值有何区别?
- 浅拷贝的实现方式有哪些?
- 深拷贝的实现方式有哪些?
本章节直接从拷贝开始说起,对于基本数据类型,引用数据类型之前的区别,可以看看上面的思维导图
引用数据类型拷贝
我们从以下三个方面来看看这块的内容
- 赋值
- 浅拷贝
- 深拷贝
赋值
引用类型的赋值是传址。其引用指向堆中的同一个对象,因此操作其中一个对象,另一个对象是会跟着一起变的。
举个栗子:
let lucy = { name: 'lucy', age: 23}let lilei = lucylilei.name = 'lilei'lilei.age = 24console.log('lucy', lucy) // lucy {name: "lilei", age: 24}console.log('lilei', lilei) // lilei {name: "lilei", age: 24}
上面栗子中可以看出来,修改了 lilei 的数据,lucy也会跟着变。这是初学者(笔者也曾这样)经常犯的一个错,后来深刻理解了对象内存的重要性!改掉了这个恶习~
那么我们该如何不让彼此之间不影响呢?
接下来我们引出了 拷贝这个概念,拷贝又分深拷贝和浅拷贝。
来看一看具体是什么和相关区别吧。
「注意:」
- 对于基本数据类型而言,并没有深浅拷贝的区别
- 深浅拷贝都是对于引用数据类型而言的
- 如果我们要赋值对象的所有属性都不是引用类型时,我们可以使用浅拷贝,遍历并复制,最后返回一个对象
「本质&使用场景」:都是复杂对象,就是说对象的属性还是对象
浅拷贝
「本质」:只复制一层对象,当对象的属性是引用类型时,实质复制的是其引用,当引用值指向发生改变时也会跟着改变
「原理」:遍历并复制,最后返回一个对象
来动手实现一个简单的浅拷贝吧
// 实现浅拷贝 for in let shallowCopy = (obj) => { let rst = {} for (let key in obj) { // 只复制本身拥有的属性(非继承过来的属性) if (obj.hasOwnProperty(key)) { rst[key] = obj[key] } } return rst}
let lucy = { name: 'lucy', age: 23, hobby: ['running', 'swimming']}let lilei = shallowCopy(lucy)lilei.name = 'lilei'lilei.age = 24lilei.hobby[0] = 'reading'console.log('lucy', lucy)// lucy {name: "lucy", age: 23, hobby: ['reading', 'swimming']}console.log('lilei', lilei)// lilei {name: "lilei", age: 24, hobby: ['reading', 'swimming']}
我们可以看到,当对象的属性是引用类型时,实质复制的是其引用,当引用值指向发生改变时也会跟着改变。
深拷贝
「实质」:深拷贝出来的对象会互不影响
「原理」:对对象中子对象进行递归拷贝
我们下面会手写一个深拷贝哈~接着往下看,会有不一样的收货!
浅拷贝的实现方式
平常用到的浅拷贝有以下几种(欢迎评论补充,互相分享进步)
- Object.assign()
- 扩展运算符(...)
- Array.prototype.slice()
Object.assign()
首先 Object.assign(target, source)
可以把n个源对象拷贝到目标对象中去(这不是本节重点讨论的内容,先一笔带过)
然后呢,Object.assign 是 ES6新增的对象方法,那么它到底是一个深拷贝还是一个浅拷贝的方法呢?
告诉你一个绝招吧(小点声)!
「拷贝对象时,第一级属性是深拷贝,以后级别浅拷贝」
举个栗子你就知道了
let lucy = { name: 'lucy', age: 23, hobby: ['running', 'swimming']}let lilei = Object.assign({}, lucy)lilei.name = 'lilei'lilei.age = 24lilei.hobby[0] = 'reading'console.log('lucy', lucy)// lucy {name: "lucy", age: 23, hobby: ['reading', 'swimming']}console.log('lilei', lilei)// lilei {name: "lilei", age: 24, hobby: ['reading', 'swimming']}
可以看出这个和咱们上面实现的那个浅拷贝的结果是一样的。
还是那句话:「拷贝对象时,第一级属性是深拷贝,以后级别浅拷贝」
是不是简简单单呢~
扩展运算符(...)
这个和 Object.assign
一样,我们来看个栗子验证一下
let lucy = { name: 'lucy', age: 23, hobby: ['running', 'swimming']}let lilei = {...lucy}lilei.name = 'lilei'lilei.age = 24lilei.hobby[0] = 'reading'console.log('lucy', lucy)// lucy {name: "lucy", age: 23, hobby: ['reading', 'swimming']}console.log('lilei', lilei)// lilei {name: "lilei", age: 24, hobby: ['reading', 'swimming']}
哦~一毛一样啊和上面。
Array.prototype.slice()
说到这个方法,我第一次看见的时候是在看 vue
源码的时候,那个时候真是涨见识(姿势)了
话不多说,看一下就知道
// Dep notify 方法Dep.prototype.notify = function notify() { var subs = this.subs.slice() // ...}
利用了slice()
方法会返回一个新的数组对象,但也是一个浅拷贝的方法。
即「拷贝对象时,第一级属性是深拷贝,以后级别浅拷贝」
看一个具体的栗子
let a1 = [1, 2, [3, 4]]let a2 = a1.slice()
a2[1] = 3a2[2][0] = 5console.log('a1', a1) // a1 (3) [1, 2, [5, 4]]console.log('a2', a2) // a2 (3) [1, 3, [5, 4]]
是不是验证了这个道理呢~
同时也要去「注意」 concat
这些会返回一个新的数组对象方法等,避免造成一些工作开发者不必要的困扰~
深拷贝的实现方式
深拷贝拷贝出来的对象互不影响,但深拷贝相比于浅拷贝速度会比较慢且开销会较大,所以考虑清楚数据结构有几层,不是很复杂的数据结构建议浅拷贝来节省性能。
看一种最简单的深拷贝实现方式
JSON.parse(JSON.stringify())
**原理:**能将json的值json化
就是指纯JSON数据,不包含循环引用,循环引用会报错
拿之前的栗子改造一下看看有哪些需要注意的地方
let lucy = { name: 'lucy', age: 23, hobby: ['running', 'swimming'], say: function() { return this.name }, other: undefined}let lilei = JSON.parse(JSON.stringify(lucy))lilei.name = 'lilei'lilei.age = 24lilei.hobby[0] = 'reading'console.log('lucy', lucy)// lucy {// name: 'lucy',// age: 23,// hobby: ['running', 'swimming'],// say: function() {// return this.name// },// other: undefined// }console.log('lilei', lilei)// lilei {age: 24, hobby: ['reading', swimming], name: 'lilei'}
可以看出来这个方法还是挺强大的。
但是也能发现一些问题
- 会忽略 undefined Symbol
- 不能序列化函数
- 不能解决循环引用的对象
- 不能处理正则
- 不能正确处理 new Date() (转换成时间戳可以拷贝)
此外,深拷贝的其他方法还有 jQuery.extend()
以及一些三方库实现的深拷贝 lodash.cloneDeep()
等等。大家感兴趣可自行了解,继续深造~
重头戏,面试常考,手写一个深拷贝,哈哈哈是不是就等这个呢~
我们改造一下上面的浅拷贝
递归实现深拷贝
// 判断边界, null 这个特殊情况let isObject = obj => typeof obj === 'object' && obj !== null
// 递归实现深拷贝let deepClone = (obj) => { // 先判断是数组还是对象 let newObj = Array.isArray(obj) ? [] : {} if (isObject(obj)) { for (let key in obj) { if (obj.hasOwnProperty(key)) { if (isObject(obj[key])) { // 递归调用每一层 newObj[key] = deepClone(obj[key]) } else { newObj[key] = obj[key] } } } } return newObj}
let aa = { name: 'aa', car: ['宝马', '奔驰'], driver: function () { }, age: undefined}let bb = deepClone(aa) // 全部拷贝了一份
bb.name = 'bb'bb.age = 20bb.driver = 'xxx'console.log(bb) // { name: 'bb', car: [ '宝马', '奔驰' ], driver: 'xxx', age: 20 }console.log(aa)// { name: 'aa', car: [ '宝马', '奔驰' ], driver: function() {}, age: undefined }
可以看出来,咱们这个递归实现的深拷贝,规避掉了 上面 JSON.parse(JSON.stringify())
的一些弊端。但是还存在一些问题
- 循环检测的问题
- 拷贝一个Symbol类型的值又该怎么解决?
- 如何解决递归爆栈的问题
哈希表
针对于循环检测,我们可以使用哈希检测的方法,比如设置一个数组或者是已经拷贝的对象,当检测到对象已经存在哈希表时,就去除该值。
let isObject = obj => typeof obj === 'object' && obj !== null;let deepClone = (source, hash = new WeakMap()) => { if (!isObject(source)) return source // 非对象返回自身 if (hash.has(source)) return hash.get(source) // 新增检测, 查哈希表 let target = Array.isArray(source) ? [] : {} hash.set(source, target) // 设置哈希表值 for (let key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = isObject(source[key]) ? deepClone(source[key], hash) : source[key]; // 传入哈希表 } } return target}let obj = { a: 1, b: { c: 2, d: 3 }}obj.a = obj.b;obj.b.c = obj.a;let clone_obj = deepClone(obj)console.log(clone_obj)
上面实现有点难度,如果未能一下看透,不妨先跳过,完成之前的那个深拷贝就够了,当然,我喜欢不惧困难的人~
剩下的两个就交给喜欢深度思考的人来去头脑风暴一下吧。
最后总结一下
和原数据是否指向同一个对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 | |
---|---|---|---|
赋值 | 是 | 改变会影响原数据 | 改变会影响原数据 |
浅拷贝 | 否 | 改变「不会」影响原数据 | 改变会影响原数据 |
深拷贝 | 是 | 改变「不会」影响原数据 | 改变「不会」影响原数据 |
写在最后
❝
享受过程带来的喜悦,学会去克服自己的缺点!
❞
深拷贝的缺点_拷贝?还傻傻分不清深浅?相关推荐
- 看完这篇你还敢说分不清 Java 类 对象 实例 变量间的区别?
看完这篇你还敢说分不清 Java 类 对象 实例 变量间的区别? 什么是类? 什么是对象? 什么是变量? 对象和类的关系: Java中的类: Java中的对象 Java中的变量 Java 中调用 对象 ...
- 国防大学计算机学院,国防大学和国防科技大学是同一所学校吗?很多人傻傻都分不清!...
国防大学和国防科技大学,这两所大学名字相近,极易混淆,在很多网站搜索"国防大学录取分数线",出来的全是国防科技大学的的高考录取分数线,所以给广大考生带来了很大困惑,难道国防大学和国 ...
- arcgis直方图工具在哪_这些分析工具都分不清?别说你懂数据分析!
举一个例子,铅笔.钢笔.圆珠笔.水彩笔.中性笔,它们的区别是什么?同理,spss.sas.excel .python.powerbi ,这些常见的数据分析工具有什么区别?很显然,这两个问题是有共性的. ...
- aop point 只能获取到map嘛_面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?...
这篇文章会从下面从以下几个问题展开对 IoC & AOP 的解释 什么是 IoC? IoC 解决了什么问题? IoC 和 DI 的区别? 什么是 AOP? AOP 解决了什么问题? AOP 为 ...
- 书脊开胶了用什么胶粘_鞋子开胶还傻傻用502粘?劝你别做无用功,用这笨招天天穿新鞋...
大部分人换鞋的原因都不是鞋子落伍了,也不是不喜欢穿了,而是鞋子脱胶了.鞋子脱胶后完全没法穿了,几乎70%的人鞋子脱胶后都会选择放弃,转而去挑选一双新鞋.还有的人呢就会想办法把鞋子修好,毕竟花钱买来的鞋 ...
- Session/Cookie/Token还傻傻分不清?
Cookie.Session.Token 傻傻分不清 Session/Cookie/Token 还傻傻分不清? 相信项目中用JWT Token的应该不在少数,但是发现网上很多文章对 token 的介绍 ...
- 国家电网和南方电网还傻傻分不清?
参看:都2020年了,国家电网和南方电网还傻傻分不清? 一.名称不同 一个叫南方电网,一个叫国家电力电网,虽然都是电网,但是区别还是很大的 而且成立时间不一样:国家电力电网有限公司成立于2002年12 ...
- cdn厂商 同兴万点_同兴万点:TXNetworks和CDNetworks让我们傻傻分不清
原标题:同兴万点:TXNetworks和CDNetworks让我们傻傻分不清 在2008年2月25日成立的同兴万点,公司全称为同兴万点(北京)网络技术有限公司(TXNetworks),一直专注于CDN ...
- 2021年了,`IEnumerator`、`IEnumerable`接口还傻傻分不清楚?
IEnumerator.IEnumerable这两个接口单词相近.含义相关,傻傻分不清楚. 入行多年,一直没有系统性梳理这对李逵李鬼. 最近本人在怼着why神的<其实吧,LRU也就那么回事> ...
最新文章
- 竖直菜单 html,jQuery实现的网页竖向菜单效果代码
- mask rcnn训练自己的数据集
- [转]EXP-00056: 遇到 ORACLE 错误 31600
- lay和lied_lie和lay的区别和用法是什么
- delimiters 插值 选项
- 【基础部分】之FTP相关配置
- linux启动mysql_【数据库】MySQL数据库入门学习
- 家居灯光控制系统设计 android,基于Android的室内照明控制系统设计与实现
- MySql 常见错误代码大全
- 家人重病什么心情都没了
- python configure函数 循环_使用python统计git仓库中频繁修改的热点函数
- Linux 命令(101)—— bc 命令
- 浸油式服务器散热系统,一种服务器散热系统
- Illustrator 教程,如何在 Illustrator 中使用铅笔工具绘图?
- Learning to Track at 100 FPS with Deep Regression Networks 论文笔记
- 用两个栈实现队列(Java)
- 逃离北上广:你以为回到小城市就非常幸福了吗?
- 【报告笔记】大数据与人工智能的伦理挑战
- shader graph落在地面的水滴涟漪效果制作思路
- Windows7重装系统后文件夹权限的混乱
热门文章
- 华为在中国建立其全球最大的网络安全透明中心
- 谷歌这波操作,预警了什么信号??
- 混合云异军突起 英特尔的全“芯”体验为企业保驾护航
- 19年兰州大学计算机分数线,兰州大学2019年在广东省录取分数线
- 装linux服务器进去配置界面,在CentOS 8 Linux上安装和配置SuiteCRM的步骤
- 达梦数据库连接(单机、多实例、多数据源版本)
- Docker JFrog Artifactory 7.27.10 maven私服(搭建篇)
- 第四篇:断路器(Hystrix)(Finchley版本)V2.0_dev
- node包管理器npm常用命令
- ios开发 热搜词demo_手机app如何开发