带你彻底搞清楚JavaScript中的深拷贝、浅拷贝和循环引用

目录

一、为什么有深拷贝和浅拷贝?

     这个要从js中的数据类型说起,js中数据类型分为基本数据类型引用数据类型

    基本类型值指的是那些保存在内存中的简单数据段,即这种值是完全保存在内存中的一个位置。包含NumberStringBooleanNullUndefined ,Symbol

    引用类型值指的是那些保存在内存中的对象,所以引用类型的值保存的是一个指针,这个指针指向存储在中的一个对象。除了上面的 6 种基本数据类型外,剩下的就是引用类型了,统称为 Object 类型。细分的话,有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等。

    正因为引用类型的这种机制, 当我们从一个变量向另一个变量复制引用类型的值时,实际上是将这个引用类型在内存中的引用地址复制了一份给新的变量,其实就是一个指针。因此当操作结束后,这两个变量实际上指向的是同一个在内存中的对象,改变其中任意一个对象,另一个对象也会跟着改变。

带你彻底搞清楚JavaScript中的深拷贝、浅拷贝和循环引用

    因此深拷贝和浅拷贝只发生在引用类型中。简单来说他们的区别在于:

1. 层次

  • 浅拷贝 只会将对象的各个属性进行依次复制,并不会进行递归复制,也就是说只会赋值目标对象的第一层属性。
  • 深拷贝不同于浅拷贝,它不只拷贝目标对象的第一层属性,而是递归拷贝目标对象的所有属性。

2. 是否开辟新的栈

  • 浅拷贝 对于目标对象第一层为基本数据类型的数据,就是直接赋值,即「传值」;而对于目标对象第一层为引用数据类型的数据,就是直接赋存于栈内存中的堆内存地址,即「传址」,并没有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变,
  • 深拷贝 而深复制则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

二、浅拷贝

以下是实现浅拷贝的几种实现方式:

1.Array.concat()

   const arr = [1,2,3,4,[5,6]];
   const copy = arr.concat(); \ 利用concat()创建arr的副本
   
   \改变基本类型值,不会改变原数组
   copy[0] = 2; 
   arr; //[1,2,3,4,[5,6]];

   \改变数组中的引用类型值,原数组也会跟着改变
   copy[4][1] = 7;
   arr; //[1,2,3,4,[5,7]];
   

能实现类似效果的还有slice()和Array.from()等,大家可以自己尝试一下~

2.Object.assign()

const obj1 = {x: 1, y: 2};
const obj2 = Object.assign({}, obj1);

obj2.x = 2; \修改obj2.x,改变对象中的基本类型值
console.log(obj1) //{x: 1, y: 2} //原对象未改变
console.log(obj2) //{x: 2, y: 2}
const obj1 = {
    x: 1, 
    y: {
        m: 1
    }
};
const obj2 = Object.assign({}, obj1);

obj2.y.m = 2; \修改obj2.y.m,改变对象中的引用类型值
console.log(obj1) //{x: 1, y: {m: 2}} 原对象也被改变
console.log(obj2) //{x: 2, y: {m: 2}}

三、深拷贝

1.JSON.parse()和JSON.stringify()

const obj1 = {
    x: 1, 
    y: {
        m: 1
    }
};
const obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}

obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}} 原对象未改变
console.log(obj2) //{x: 2, y: {m: 2}}

这种方法使用较为简单,可以满足基本日常的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是有以下几个缺点:

  • undefined、任意的函数、正则表达式类型以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时);
  • 它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;
  • 如果对象中存在循环引用的情况无法正确处理。

2.递归

function deepCopy1(obj) {
    // 创建一个新对象
    let result = {}
    let keys = Object.keys(obj),
        key = null,
        temp = null;

    for (let i = 0; i < keys.length; i++) {
        key = keys[i];    
        temp = obj[key];
        // 如果字段的值也是一个对象则递归操作
        if (temp && typeof temp === 'object') {
            result[key] = deepCopy(temp);
        } else {
        // 否则直接赋值给新对象
            result[key] = temp;
        }
    }
    return result;
}

const obj1 = {
    x: {
        m: 1
    },
    y: undefined,
    z: function add(z1, z2) {
        return z1 + z2
    },
    a: Symbol("foo")
};

const obj2 = deepCopy1(obj1);
obj2.x.m = 2;

console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}

四、循环引用

看似递归已经完全解决我们的问题了,然而还有一种情况我们没考虑到,那就是循环引用

1.父级引用

这里的父级引用指的是,当对象的某个属性,正是这个对象本身,此时我们如果进行深拷贝,可能会在子元素->父对象->子元素…这个循环中一直进行,导致栈溢出。比如下面这个例子:

 const obj1 = {
    x: 1, 
    y: 2
};
obj1.z = obj1;

const obj2 = deepCopy1(obj1); \栈溢出

解决办法是:只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可,可以修改上面的deepCopy1函数:

function deepCopy2(obj, parent=null) {
    //创建一个新对象
    let result = {};
    let keys = Object.keys(obj),
         key = null,
         temp = null,
         _parent = parent;
    //该字段有父级则需要追溯该字段的父级
    while(_parent) {
        //如果该字段引用了它的父级,则为循环引用
        if(_parent.originParent === obj) {
            //循环引用返回同级的新对象
            return _parent.currentParent;
        }
        _parent = _parent.parent
    }
    for(let i=0,len=keys.length;i<len;i++) {
        key = keys[i]
        temp = obj[key]
        // 如果字段的值也是一个新对象
        if(temp && typeof temp === 'object') {
            result[key] = deepCopy(temp, {
                //递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent,方便追溯循环引用
                originParent: obj,
                currentParent: result,
                parent: parent
            });
        } else {
            result[key] = temp;
        }
    }
    return result;
}

const obj1 = {
    x:1
}
obj1.z = obj1;

const obj2 = deepCopy2(obj1);

2. 同级引用

假设对象obj有a,b,c三个子对象,其中子对象c中有个属性d引用了对象obj下面的子对象a。

const obj= {
    a: {
        name: 'a'
    },
    b: {
        name: 'b'
    },
    c: {

    }
};
c.d.e = obj.a;

此时c.d.e和obj.a 是相等的,因为它们引用的是同一个对象

  console.log(c.d.e === obj.a); //true

如果我们调用上面的deepCopy2函数

const copy = deepCopy2(obj);
console.log(copy.a); // 输出: {name: "a"}
console.log(copy.d.e);// 输出: {name: "a"}
console.log(copy.a === copy.d.e); // 输出: false

以上表现我们就可以看出,虽然opy.a 和copy.d.e在字面意义上是相等的,但二者并不是引用的同一个对象,这点上来看对象copy和原对象obj还是有差异的。

这种情况是因为obj.a并不在obj.d.e的父级对象链上,所以deepCopy2函数就无法检测到obj.d.e对obj.a也是一种引用关系,所以deepCopy2函数就将obj.a深拷贝的结果赋值给了copy.d.e。

解决方案:父级的引用是一种引用,非父级的引用也是一种引用,那么只要记录下对象A中的所有对象,并与新创建的对象一一对应即可。

function deepCopy3(obj) {
    // hash表,记录所有的对象的引用关系
    let map = new WeakMap();
    function dp(obj) {
        let result = null;
        let keys = Object.keys(obj);
        let key = null,
            temp = null,
            existobj = null;

        existobj = map.get(obj);
        //如果这个对象已经被记录则直接返回
        if(existobj) {
            return existobj;
        }

        result = {}
        map.set(obj, result);

        for(let i =0,len=keys.length;i<len;i++) {
            key = keys[i];
            temp = obj[key];
            if(temp && typeof temp === 'object') {
                result[key] = dp(temp);
            }else {
                result[key] = temp;
            }
        }
        return result;
    }
    return dp(obj);
}

const obj= {
    a: {
        name: 'a'
    },
    b: {
        name: 'b'
    },
    c: {

    }
};
c.d.e = obj.a;

const copy = deepCopy3(obj);

五、总结

    其实拷贝的方式还有很多种,比如jquery中的$.extend,lodash的_.cloneDeep等等,关于拷贝中还有很多问题值得深究,比如正则类型的值如何拷贝,原型上的属性如何拷贝,这些我都会慢慢研究哒!大家也可以思考一下~

最后,欢迎点赞收藏!! 关注 「大厂必背面试题」 专栏,里面有很多让你开心到哇塞的面试题讲解。

原文链接:https://juejin.cn/post/7233424944758571068 作者:白哥学前端

(0)
我心飞翔我心飞翔
0 0
ES6 – 数值的拓展
上一篇 2023年5月16日 上午10:43
面试题:小男孩毕业之小厂面试(第一次面试)
下一篇 2023年5月16日 上午10:53

相关推荐

  • 交互延迟上的探索与最佳实践-RUM 2024年3月28日
  • 高阶组件实现组件配置化 2023年3月21日
  • jquery事件处理中bind()live()delegate()和on()有什么区别 2019年8月15日
  • 私信好多,大学生要不要学习游戏引擎? 2023年12月26日
  • [保姆级] Vue3 开发文档,不会的再不看要遭老罪咯 2023年4月10日
  • 前端性能指标和优化目标 2020年9月26日
  • 前端框架对比系列之vue和react的页面角色权限控制(一) 2024年3月20日
  • 使用多个二维码传输数据 2024年1月29日
  • Matroska解封装原理与实践 2024年4月17日
  • ECMAScript日常总结–ES2022(ES13) 2023年12月16日

发表回复

登录后才能评论

近期文章

  • 在 Vue 中实现类似 ahooks useRequest 的异步请求 Hook
  • 使用 nuqs 在 Next.js 中管理 URL 查询字符串状态
  • JavaScript的Iterator和for…of循环
  • 手把手使用Blender+ThreeJS制作跨栏小游戏
  • CSS的三大魔法特性:层叠、继承与优先级,让你的网页设计如虎添翼!
  • docusaurus中引入shiki实现代码高亮
  • 浏览器中如何获取用户网络状态
  • 完全掌握vue全家桶单元测试 : 6. 深入理解组件测试
  • InqureJS:手搓脚手架必备!让你的命令行交互花里胡哨!
  • 【vite.config.ts】(多环境配置)设置环境文件 .env.local
  • 腾讯、阿里、B站最新面经汇总,有的妥妥的凉经
  • 带你深入Vue.js开发实战,从复杂列表的样式到性能优化
  • 『Django』路由urls
  • go语言如何实现协程的抢占式调度的?
  • 字节面试:如何解决MQ消息积压问题?
  • web server apache tomcat11-16-mbean
  • web server apache tomcat11-17-default-servlet
  • ✅MySQL的脏读、幻读、不可重复度是什么
  • tailwindcss Vue项目CSS开发体验
  • 简单的题,内涵不简单
  • AOP使用案例-记录操作日志
  • 计算机基础系列 —— 虚拟机代码翻译器(2)
  • flea-common使用之本地国际化实现
  • 锁、mvcc、隔离级别、(脏读、不可重复读、幻读)理解
  • 【java】使用表达式处理数据 – Aviator
  • python数据类型-字符串
  • 原生桥接方式:深入了解JavaScript Bridge(JsBridge)
  • 邀请函 | Pulsar Meetup 深圳 2024
  • TTFB时间太长怎么办
  • 超实用!2024年必看的10个导航栏设计

知识题库

  • 手把手教你vue项目接入漂亮的验证码
  • 校招前端二面经典react面试题及答案_2023-03-13
  • 字节前端二面react面试题(边面边更)_2023-03-13
  • 滴滴前端一面常考vue面试题(持续更新中)_2023-03-13
  • new Vue的时候到底做了什么_2023-03-13
  • 滴滴前端高频vue面试题(边面边更)_2023-03-13
  • 为啥我要选用Element作为wljslmz.cn子系统的UI框架?
  • vue-cli 是怎么配置babel的?
  • 面试官问我按钮级别权限怎么控制,我说v-if,面试官说再见
  • Vue项目迁移小程序,实操干货分享
  • Element ui: form表单使用
  • vue中加入百度统计
  • 前端开发:如何写一手漂亮的 Vue
  • Vue-travel学习笔记
  • 前端开发者不得不知道的18个常用的网站
  • vue-awesome-swiper的用法&同一页面有多个swiper如何使用
  • 结合 Bootstrap + Vue 组件实现 Laravel 异步分页功能
  • 在 Vue.js 中通过计算属性动态设置属性值
  • Vue 组件注册:基本使用和组件嵌套
  • Vue 组件插槽:父子组件间的内容分发和插槽作用域

玻璃钢生产厂家商场商品美陈布置庆阳大型玻璃钢雕塑安装情人节商场美陈玻璃钢韦陀雕塑铸造厂玻璃钢加菲猫造型雕塑张掖仿真人物玻璃钢雕塑多少钱拉丝玻璃钢雕塑价格行情广东美陈商场红色玻璃钢雕塑销售价格马鞍山玻璃钢雕塑价格新乡肖像玻璃钢雕塑公司闵行玻璃钢雕塑厂商场美陈趋势玻璃钢景观雕塑公司上城区玻璃钢金属雕塑设计唐山动物玻璃钢雕塑公司商丘公园景观校园玻璃钢雕塑厂家玻璃钢动物雕塑厂商出售莆田玻璃钢花盆雕塑公司广西卡通玻璃钢动物马雕塑商场中庭美陈装修效果图商场灯光美陈定制工厂德州玻璃钢仿真水果雕塑价格丽水玻璃钢雕塑定做西宁玻璃钢景观雕塑佛山动物玻璃钢雕塑黄山户外玻璃钢雕塑定制濮阳玻璃钢雕塑小品玻璃钢西瓜雕塑造价江苏节日商场美陈香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化