深拷贝的缺点_拷贝?还傻傻分不清深浅?

「引言」

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

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

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

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

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

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

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

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

9add1020136171d2f67e01da3a73799f.png

引用数据类型拷贝

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

  • 赋值
  • 浅拷贝
  • 深拷贝

赋值

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

举个栗子:

let lucy = {
    name: 'lucy',
    age: 23
}
let lilei = lucy
lilei.name = 'lilei'
lilei.age = 24
console.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 = 24
lilei.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 = 24
lilei.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 = 24
lilei.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] = 3
a2[2][0] = 5
console.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 = 24
lilei.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 = 20
bb.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)

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

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

最后总结一下

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

写在最后

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/528895.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实现文件中名词的统计计数_通过勤哲EXCEL和Excel中的rank函数实现排名统计

如今,信息化已成为各行业企业转型和发展的关键所在,信息化技术最明显的特点是企业不同部门的人在信息技术的支撑下,可以利用丰富的资源与工具展开协作学习,在相对自由的模式和环境下,改变传统设计相对局限、固定思维的…

安徽关节式焊接机器人_上下料机器人的重要性体现在哪里?它有哪些优势?

上下料机器人是工业机器人家族中从事加工应用的重要成员之一,能满足“快速、大批量加工节拍”、“节省人力成本”、“提高生产效率”等要求,成为越来越多中心企业的理想选择。那么,上下料机器人有哪些特征和优势?企业主应该如何选…

vision软件_Roboguide软件:高速拾取仿真工作站相机与工具添加与配置

上一期为大家介绍了机器人高速拾取仿真工作站中工件、视觉校准板、输送带等外围设备的添加与配置,本期再来为大家介绍一下相机、机器人工具的添加与配置。一、相机添加与配置1.左侧浏览树中右击“Sensor Units”,在弹出的快捷菜单中依次点击“Add Sensor…

arduino tft 方向_ESP32在Arduino环境下玩转 LVGL,ESP32移植LVGL详细教程

微信关注 “DLGG创客DIY”设为“星标”,重磅干货,第一时间送达。❝转载自慕容流年 https://me.csdn.net/qq_41868901❞1. LVGL简介LittlevGL是一个免费的开源图形库,提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素、漂亮的…

linux内核开文件系统,内核必须懂(二): Linux文件系统初探

目录前言文件系统结构新建文件和inode文件创建过程inode解析打开文件参考最后前言这次来说文件系统. 文件系统是非常重要的, 提高磁盘使用率, 减小磁盘磨损等等都是文件系统要解决的问题. 市面上的文件系统也是数不胜数, 比较常用的像ext4, xfs以及ntfs等等, 国内的像鹅厂的tfs…

gps卫星位置计算程序matlab_科研项目 | BD/ GPS卫星导航仿真测试系统研究

一、项目背景在移动互联和互联网的时代,卫星导航定位已经成为生活中不可或缺的部分。目前,我国的交通、银行、电力、采矿、测绘等部门以及大众化市场都需要大量的定位、授时服务。许多企业也形成了覆盖卫星导航相关材料及元器件、卫星导航芯片、卫星导航…

Linux输入密码接口,Linux下搭建接口自动化测试平台

前言我们今天来学习一下在Linux下如何搭建基于HttpRunner开发的接口自动化测试平台吧!需要在Linux上提前准备的环境(下面是本人搭建时的环境):1,Python 3.6.8 (可参考随笔:Linux学习6-安装Python3.6)2,MySQL 5.7(可参考…

linux下查看windows文件夹大小,如何从Windows命令行检查文件夹的大小?

我意识到这个问题要求使用进行文件大小分析CMD line。但是,如果您愿意使用它,PowerQuery (Excel add-in, versions 2010)则可以创建一些非常引人注目的文件大小分析。下面的脚本可以粘贴到空白查询中;您唯一需要做的就是添加一个名为“ param…

图像语义分割_图像语义分割(9)-DeepLabV3: 再次思考用于图像语义分割的空洞卷积...

论文地址 :Rethinking Atrous Convolution for Semantic Image Segmentation论文代码:Github链接1. 摘要文章主要的工作:使用空洞卷积来调整滤波器的感受野并控制特征图分辨率使用不同空洞率的空洞卷积的串联或者并行操作来分割不同尺度的目标…

sql replace 双引号变单引号_sql-汇总、排序以及分析思路

一、汇总函数注:汇总函数,如果输入是列名,计算会把null 排除在外,count(*)对所有的行进行计数二、如何用sql解决业务问题注1:空值(null)的排序,排序是最小的,例如select * from tea order by tea_name注2&a…

openwrt是嵌入式linux,非常方便的OpenWrt的嵌入式Linux开发环境

今天听中央经济广播电台说,今年有559万大学毕业生,大学生的就业形式严峻.我想那些精通Linux开发的毕业大学生们,估计一点也不用担心,因为他们已经不再是一颗大白菜了.最近在帮一个朋友把OpenWrt移植到一块单板上,有一些心得,分享给大家.OpenWrt是一个很好的学习Linux的平台,而且…

php多个表中查找数据_HeidiSQL 免费的可视化数据库管理工具

HeidiSQL是一款免费的软件,并且易于学习和使用。HeidiSQL让你从数据库内可视化的读写数据、结构体。它支持MariaDB、MySQL、Microsoft SQL、PostgreSQL等数据库。功能免费且开源内置中文在同个窗口连接多个服务支持以命令行形式连接服务连接支持SSH、SSL创建修改表、…

嵌入式linux启动根文件系统,嵌入式Linux根文件系统制作和挂载

嵌入式Linux系统由三部分组成: uboot、kernel、根文件系统, 还是这张老图这里的根文件系统可以说是包含两个部分: 一个是根,一个是文件系统那么什么是根呢?哈哈 其实根表示的就是第一个的意思下面贴张图看看整个根文件…

sqlite 查询 支持多用户同时_SQLite支持多进程同时操作数据库文件吗?

多个进程可以同时打开同一个数据库。多个进程可以同时执行SELECT。但是,只有一个进程可以随时对数据库进行更改。SQLite使用读/写锁来控制对数据库的访问。(在Win95 / 98 / ME下,缺少对读写锁的支持,而是使用概率模拟。)但请注意:…

pgsql 查询每天的最后一条_Qamp;A | 如何允许他人查询表单数据?

小金Q&A来啦!这一期小金Q&A中,小金收集到近期大家关注最多的几个问题。小金将定期为小金人们答疑解惑,帮助你每天用好小金一点。●Q&A●问:如何批量设置新数据微信提醒?答:点击右上角头像 个人…

linux 解压tar.jz,linux系统压缩文件和解压缩命令

linux系统压缩文件和解压缩命令tar命令   解包:tar zxvf FileName.tar打包:tar czvf FileName.tar DirName gz命令   解压1:gunzip FileName.gz解压2:gzip -d FileName.gz压缩:gzip FileName.tar.gz 和 .tgz解压&a…

linux fall delay 10,Cell的Rise delay和Fall delay、Rise transition和fall transition

Cell的Rise delay和Fall delay、Rise transition和Fall transition介绍:如下图所示为一个Cell的lib中关于cell_rise/cell_fall、rise_transition/fall_transition的描述:在NLDM模型的.lib中,cell_rise/cell_fall它描述的是Cell从输入到输出的…

keil流水灯c语言程序两个一起亮,我用keil c51编了一个流水灯程序,编译无误却只有第一和第二个灯亮了,最后停在第一个灯处不动...

满意答案zJ张俭2014.02.09采纳率:43% 等级:12已帮助:10469人你有几个错误:1 你的定时器没有重装,导致第一次中断和后面的中断时间不一样,第一次时间短,后面的时间一直是以65536个周期中断的.2 你在中断函数里用了 for 循环,for循环是一下子…

pyquery获取不到网页完整源代码_python动态网页爬取:爬取pexel上的图片

前言同样的,我们在写一个爬虫前要明确自己想要爬取的东西是什么,明确下载目标数据在浏览器的操作如何对于动态网页的爬取,在网页地址不变的情况下,我们首先要明确如何获取AJAX请求首先我们看看这个网站pexel打开页面后再Chrome浏览…

W ndows找不到explorer,windows找不到explorer.exe的处理教程

很多网友入手windows过程里找不到explorer.exe,很是烦恼,为了帮助大家排忧解难,这里小编就分享了windows找不到explorer.exe的处理教程,希望可以帮助到大家。windows找不到explorer.exe的处理教程1、按winr打开运行窗口&#xff0…