「引言」

臣闻求木之长者,必固其根本;欲流之远者,必浚其泉源。

---- 魏征 《谏太宗十思疏》

或许你会问到,网上已经把深浅拷贝(算一个面试的高频考点了吧)的文章都快写烂了,为什么自己还要重新操刀写一遍呢!?

首先,一些文章,讲不清也道不明本质;另外,确实有很优秀的人写的很是生动,让我直接看到了风景,却不知道沿途是不是也有自己错过的美景,唯有尝试过,才会真正成为自己的~

首先,我们先来看一张笔者整理的脑图,梳理一下~

希望通过本文的总结,你会有以下几点收获:

  • 什么是深浅拷贝?他们与赋值有何区别?
  • 浅拷贝的实现方式有哪些?
  • 深拷贝的实现方式有哪些?

本章节直接从拷贝开始说起,对于基本数据类型,引用数据类型之前的区别,可以看看上面的思维导图


引用数据类型拷贝

我们从以下三个方面来看看这块的内容

  • 赋值
  • 浅拷贝
  • 深拷贝

赋值

引用类型的赋值是传址。其引用指向堆中的同一个对象,因此操作其中一个对象,另一个对象是会跟着一起变的。

举个栗子:

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也会跟着变。这是初学者(笔者也曾这样)经常犯的一个错,后来深刻理解了对象内存的重要性!改掉了这个恶习~

那么我们该如何不让彼此之间不影响呢?

接下来我们引出了 拷贝这个概念,拷贝又分深拷贝和浅拷贝。

来看一看具体是什么和相关区别吧。

「注意:」

  1. 对于基本数据类型而言,并没有深浅拷贝的区别
  2. 深浅拷贝都是对于引用数据类型而言的
  3. 如果我们要赋值对象的所有属性都不是引用类型时,我们可以使用浅拷贝,遍历并复制,最后返回一个对象

「本质&使用场景」:都是复杂对象,就是说对象的属性还是对象

浅拷贝

「本质」:只复制一层对象,当对象的属性是引用类型时,实质复制的是其引用,当引用值指向发生改变时也会跟着改变

「原理」:遍历并复制,最后返回一个对象

来动手实现一个简单的浅拷贝吧

// 实现浅拷贝 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())的一些弊端。但是还存在一些问题

  1. 循环检测的问题
  2. 拷贝一个Symbol类型的值又该怎么解决?
  3. 如何解决递归爆栈的问题
哈希表

针对于循环检测,我们可以使用哈希检测的方法,比如设置一个数组或者是已经拷贝的对象,当检测到对象已经存在哈希表时,就去除该值。

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)

上面实现有点难度,如果未能一下看透,不妨先跳过,完成之前的那个深拷贝就够了,当然,我喜欢不惧困难的人~

剩下的两个就交给喜欢深度思考的人来去头脑风暴一下吧。

最后总结一下

和原数据是否指向同一个对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会影响原数据 改变会影响原数据
浅拷贝 改变「不会」影响原数据 改变会影响原数据
深拷贝 改变「不会」影响原数据 改变「不会」影响原数据

写在最后

享受过程带来的喜悦,学会去克服自己的缺点!

深拷贝的缺点_拷贝?还傻傻分不清深浅?相关推荐

  1. 看完这篇你还敢说分不清 Java 类 对象 实例 变量间的区别?

    看完这篇你还敢说分不清 Java 类 对象 实例 变量间的区别? 什么是类? 什么是对象? 什么是变量? 对象和类的关系: Java中的类: Java中的对象 Java中的变量 Java 中调用 对象 ...

  2. 国防大学计算机学院,国防大学和国防科技大学是同一所学校吗?很多人傻傻都分不清!...

    国防大学和国防科技大学,这两所大学名字相近,极易混淆,在很多网站搜索"国防大学录取分数线",出来的全是国防科技大学的的高考录取分数线,所以给广大考生带来了很大困惑,难道国防大学和国 ...

  3. arcgis直方图工具在哪_这些分析工具都分不清?别说你懂数据分析!

    举一个例子,铅笔.钢笔.圆珠笔.水彩笔.中性笔,它们的区别是什么?同理,spss.sas.excel .python.powerbi ,这些常见的数据分析工具有什么区别?很显然,这两个问题是有共性的. ...

  4. aop point 只能获取到map嘛_面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?...

    这篇文章会从下面从以下几个问题展开对 IoC & AOP 的解释 什么是 IoC? IoC 解决了什么问题? IoC 和 DI 的区别? 什么是 AOP? AOP 解决了什么问题? AOP 为 ...

  5. 书脊开胶了用什么胶粘_鞋子开胶还傻傻用502粘?劝你别做无用功,用这笨招天天穿新鞋...

    大部分人换鞋的原因都不是鞋子落伍了,也不是不喜欢穿了,而是鞋子脱胶了.鞋子脱胶后完全没法穿了,几乎70%的人鞋子脱胶后都会选择放弃,转而去挑选一双新鞋.还有的人呢就会想办法把鞋子修好,毕竟花钱买来的鞋 ...

  6. Session/Cookie/Token还傻傻分不清?

    Cookie.Session.Token 傻傻分不清 Session/Cookie/Token 还傻傻分不清? 相信项目中用JWT Token的应该不在少数,但是发现网上很多文章对 token 的介绍 ...

  7. 国家电网和南方电网还傻傻分不清?

    参看:都2020年了,国家电网和南方电网还傻傻分不清? 一.名称不同 一个叫南方电网,一个叫国家电力电网,虽然都是电网,但是区别还是很大的 而且成立时间不一样:国家电力电网有限公司成立于2002年12 ...

  8. cdn厂商 同兴万点_同兴万点:TXNetworks和CDNetworks让我们傻傻分不清

    原标题:同兴万点:TXNetworks和CDNetworks让我们傻傻分不清 在2008年2月25日成立的同兴万点,公司全称为同兴万点(北京)网络技术有限公司(TXNetworks),一直专注于CDN ...

  9. 2021年了,`IEnumerator`、`IEnumerable`接口还傻傻分不清楚?

    IEnumerator.IEnumerable这两个接口单词相近.含义相关,傻傻分不清楚. 入行多年,一直没有系统性梳理这对李逵李鬼. 最近本人在怼着why神的<其实吧,LRU也就那么回事> ...

最新文章

  1. 竖直菜单 html,jQuery实现的网页竖向菜单效果代码
  2. mask rcnn训练自己的数据集
  3. [转]EXP-00056: 遇到 ORACLE 错误 31600
  4. lay和lied_lie和lay的区别和用法是什么
  5. delimiters 插值 选项
  6. 【基础部分】之FTP相关配置
  7. linux启动mysql_【数据库】MySQL数据库入门学习
  8. 家居灯光控制系统设计 android,基于Android的室内照明控制系统设计与实现
  9. MySql 常见错误代码大全
  10. 家人重病什么心情都没了
  11. python configure函数 循环_使用python统计git仓库中频繁修改的热点函数
  12. Linux 命令(101)—— bc 命令
  13. 浸油式服务器散热系统,一种服务器散热系统
  14. Illustrator 教程,如何在 Illustrator 中使用铅笔工具绘图?
  15. Learning to Track at 100 FPS with Deep Regression Networks 论文笔记
  16. 用两个栈实现队列(Java)
  17. 逃离北上广:你以为回到小城市就非常幸福了吗?
  18. 【报告笔记】大数据与人工智能的伦理挑战
  19. shader graph落在地面的水滴涟漪效果制作思路
  20. Windows7重装系统后文件夹权限的混乱

热门文章

  1. 华为在中国建立其全球最大的网络安全透明中心
  2. 谷歌这波操作,预警了什么信号??
  3. 混合云异军突起 英特尔的全“芯”体验为企业保驾护航
  4. 19年兰州大学计算机分数线,兰州大学2019年在广东省录取分数线
  5. 装linux服务器进去配置界面,在CentOS 8 Linux上安装和配置SuiteCRM的步骤
  6. 达梦数据库连接(单机、多实例、多数据源版本)
  7. Docker JFrog Artifactory 7.27.10 maven私服(搭建篇)
  8. 第四篇:断路器(Hystrix)(Finchley版本)V2.0_dev
  9. node包管理器npm常用命令
  10. ios开发 热搜词demo_手机app如何开发