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

「引言」

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

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

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

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

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

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

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

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

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,一经查实,立即删除!

相关文章

php 失去 焦点 另一个表单猎取值,同一表单如何根据某一个文本框的值 改变另一个文本框的值...

我用的是失去焦点,然后后台查询到了数据,返回回来显示不了!!$(document).ready(function(){$("#card").blur(function(){var card$("#card").val();if(card!""){$.get("{:U(guoye/yanzheng)}&…

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

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

php 选择 出现列表,php – 如何从加权列表中选择4个唯一项?

所以我有一个加权项目列表,我想从这个列表中选择4个非重复项目.Item WeightApple 5Banana 7Cherry 12...Orange 8Pineapple 50最有效的方法是什么?我最初的尝试是,如果一个已经被选中的项目出现的话,只需重新选择随后的选秀权……但是对于一个小名单,这可能会导致大…

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

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

oracle查询ora03114,求教:ora-03114错误从哪里排查?

ERP软件偶尔会报 ora-03114错误 未连接到oracle我检查数据库的根超时有关的设置dba_profiles中的如下两个参数,都是不限制connect_timeunlimitedidle_timeunlimitedsqlnet.net中,只设置了如下参数SQLNET.EXPIRE_TIME5 --这个用来清除僵死连接的监听日志文…

python大学什么专业学校_好学校的差专业和一般大学的好专业,该怎么选?我来说真话……...

假如是985,那么只能调剂专业,那么好点211能选到不错的专业。那该怎么选择?其实不单单是985/211的问题,是所有考生的任何一个分数都面临这个问题。比如清华北大被调剂,是去复交金融,还是去清华北大调剂去学环…

oracle查询表nextval,Oracle Database 建立与查询 Sequence

相关的 Oracle Database Sequence 资讯如下:建立 Sequence 语法create sequence ._sincrement by 1start with 1maxvalue 99999999999999999999minvalue 1nocycle;其中 :incerment: 每次增加的数值start : Sequence 开始数值maxvalue : Sequence 最大值nocycle …

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

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

oracle00333,Oracle数据库REDO损坏ora-00333修复手札

1.事情起因接到电话,周日意外断电,数据库起不了,报REDO CRASH,ora-00333错误。相关环境如下:RAC oracle_11.2.0.3,无备份,开归档。2 处理一到现场,既然对方没有备份,那就做一个冷备份…

oracle cannot allocate new log,Thread 1 cannot allocate new log

今天发现alter.log有以下信息:Thread 1 cannot allocate new log, sequence 6166Private strand flush not complete对于这个错误信息得解释如下:当系统要重新利用某个日志文件的时候,系统需要将该日志文件所包括的buffer cache 中的dirty bl…

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 docker权限,linux - 如何解决ubuntu中的docker权限问题? - Ubuntu问答

问题描述我已经按照here的说明安装了docker。我使用Ubuntu Trusty 14.04 (LTS) (64-bit)。安装过程中的一切都很好。另外,命令$ sudo docker run -i -t ubuntu /bin/bash可以很好地完成(在打开的控制台中键入”exit”之后。但是当我尝试执行其他操作时,我…

python虚拟人脸生成_GitHub - 597111463/seeprettyface-generator-yellow: 这是一个用StyleGAN训练出的黄种人人脸生成器...

黄种人人脸生成器注明:之前做的一些有意思的人脸生成器,现在全部开源分享出来。它的主要作用是可生成制作各类型的人脸素材,供我们任意使用且无须担心人脸版权的问题。在定制人脸上,开源的全系列生成器包括:黄种人脸生…

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

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

python构建字典实现英文大写字母与ascii编码的转换_Python:将复杂的字符串字典从Unicode转换为ASCII...

作为从JSON API调用解析的多级字典,我有很多输入.字符串都是unicode,这意味着有很多ustuff like this.我正在使用jq来处理结果,需要将这些结果转换为ASCII.我知道我可以编写一个函数来像这样转换它:def convert(input):if isinstance(input, dict):ret {}for stuff in input:r…

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

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

python多进程传递参数_急急急, Python 多进程,如何传递 epoll?

102019-06-16 15:39:41 08:00NoAnyLove 好的好的,我查了下,说 IPC 或向 worker 参数传递的东西必须要能 pickle,不然就报错,那就是 select.epoll 是不支持的。error:TypeError: cant pickle select.epoll objects。代码…

linux查看一小时之内的日志,linux – 在[timespan]内(例如最后一小时)查找日志文件中的条目...

我的服务器CPU使用率不高,我可以看到Apache正在使用太多的内存。我有一种感觉,我正在用一个IP进行DOS.-也许你可以帮我找到他?我已经使用以下行找到10个最“活跃”的IP:cat access.log | awk {print $1} |sort |uniq -c |sort -n …