一篇文章彻底说清JS的深拷贝and浅拷贝

这篇文章的受众

  • 第一类,业务需要,急需知道如何深拷贝JS对象的开发者。
  • 第二类,希望扎实JS基础,将来好去面试官前秀操作的好学者。

写给第一类读者

你只需要一行黑科技代码就可以实现深拷贝

var copyObj = {name: 'ziwei',arr : [1,2,3]
}var targetObj = JSON.parse(JSON.stringify(copyObj))此时 copyObj.arr !== targetObj.arr  已经实现了深拷贝

别着急走,利用window.JSON的方法做深拷贝存在2个缺点:

  • 如果你的对象里有函数,函数无法被拷贝下来
  • 无法拷贝copyObj对象原型链上的属性和方法

当然,你明确知道他们的缺点后,如果他的缺点对你的业务需求没有影响,就可以放心使用了,一行原生代码就能搞定。

目前我在开发业务场景中,大多还真可以忽略上面2个缺点。往往需要深拷贝的对象里没有函数,也不需要拷贝它原型链的属性。

写给第二类读者

下面我会尽可能全面的讲解清楚JS里对象的拷贝,要讲清楚拷贝,你需要一点点前置知识

你需要的前置知识:

  • 理解JS里的引用类型和值类型的区别,知道Obj存储的只是引用
  • 对原型链有基本了解

关于对象拷贝的全部:

  • 1.深拷贝、浅拷贝是什么
  • 2.深拷贝、浅拷贝在业务里的最常见的应用场景
  • 3.深拷贝和浅拷贝的实现方式
  • 4.总结与建议

1.深拷贝、浅拷贝是什么

我们讨论JS对象深拷贝、浅拷贝的前提

只有对象里嵌套对象的情况下,才会根据需求讨论,我们要深拷贝还是浅拷贝。

比如下面这种对象

var obj1 = {name: 'ziwei',arr : [1,2,3]
}

因为,如果是类似这样{name: 'ziwei'},没有嵌套对象的对象的话,就没必要区分深浅拷贝了。只有在有嵌套的对象时,深拷贝和浅拷贝才有区别

浅拷贝是什么样子的 (我们暂时不管具体如何实现,因为下面会单讲)

调用shallowCopy()后,obj2拷贝obj1所有的属性。但是obj2.arr和obj1.arr是不同的引用,指向同一个内存空间

 var obj2 = shallowCopy( obj1 , {})console.log( obj1 !== obj2 )                   // true    无论哪种拷贝,obj1和obj2一定都是2个不同的对象(内存空间不同)console.log( obj2.arr === obj1.arr )            // true   他们2个对象里arr的引用,指向【相同的】内存空间

所以, 2个obj经过拷贝后,虽然他们属性相同,也的确是不同的对象,但他们内部的obj都是指向同一个内存空间,这种我们叫浅拷贝

深拷贝是什么样子的 (我们暂时不管具体如何实现,因为下面会单讲)

调用deepCopy()后,obj2拷贝obj1所有的属性,而且obj2.arr和obj1.arr是指向不同的内存空间,

2个obj2除了拷贝了一样的属性,没有任何其他关联。

 var obj2 = deepCopy( obj1 , {})console.log( obj1 !== obj2 )                   // true    无论哪种拷贝,obj1和obj2一定都是2个不同的对象(内存空间不同)console.log( obj2.arr === obj1.arr )            // false   他们2个对象里arr的引用,指向【不同的】内存空间

所以, 2个obj经过拷贝后,除了拷贝下来相同的属性之外,没有任何其他关联的2个对象,这种我们叫深拷贝

2.深拷贝在业务里的最常见的应用场景

举个栗子,业务需求是 : 一个表格展示商品各种信息,点击【同意】时,是可以弹出对话框调整商品数量的。

这种业务需求下,我们就会用到对象的深拷贝。因为【商品表格】的属性和【调整商品表格】的属性几乎一样,我们需要拷贝。

下面的伪代码和图片就是展示使用浅拷贝存在的问题

这样得到的adjustTableArr和tableArr里,内部对象都是相同的,所以就出现了图中红线标注的情况,

当我们修改【调整商品表格】里的商品数量时,【商品表格】也跟着改变了,这并不是我们想要的


// 表格对象的数据结构
var tableArr = [{goods_name : '长袖腰背夹' , code : 'M216C239E0864' , num : '2'},{goods_name : '长袖腰背夹' , code : 'M216C240B0170' , num : '3'},{goods_name : '短塑裤' , code : 'M216D241C04106' , num : '3'},]var adjustTableArr = []                  // 调整表格用的数组for (var key in tableArr) {               // 浅拷贝adjustTableArr[key] = tableArr[key]
}

而实际上,我们希望这2个表格里的数据完全独立,互不干扰,只有在确认调整之后才刷新商品数量。

这种情况下我们就可以使用前面说的深拷贝的一行黑科技

var adjustTableArr = JSON.parse(JSON.stringify(tableArr))

还记得它的缺陷吗? 对象里的函数无法被拷贝,原型链里的属性无法被拷贝。这里就对业务没有影响,可以很方便的深拷贝。

3.深拷贝和浅拷贝的实现方式

其实JQ里已经有$.extend()函数,实现就是深拷贝和浅拷贝的功能。有兴趣的小伙伴也可以看看源码。

浅拷贝

浅拷贝比较简单,就是用for in 循环赋值

    function shallowCopy(source, target = {}) {var key;for (key in source) {if (source.hasOwnProperty(key)) {        // 意思就是__proto__上面的属性,我不拷贝target[key] = source[key];}}return target;}

深拷贝的实现

  • 深拷贝,就是遍历那个被拷贝的对象
  • 判断对象里每一项的数据类型
  • 如果不是对象类型,就直接赋值,如果是对象类型,就再次调用deepCopy,递归的去赋值。
    function deepCopy(source, target = {}) {var key;for (key in source) {if (source.hasOwnProperty(key)) {                         // 意思就是__proto__上面的属性,我不拷贝if (typeof(source[key]) === "object") {               // 如果这一项是object类型,就递归调用deepCopytarget[key] = Array.isArray(source[key]) ? [] : {};deepCopy(source[key], target[key]);} else {                                            // 如果不是object类型,就直接赋值拷贝target[key] = source[key];}}}return target;}

以上的无论深、浅拷贝,都用了source.hasOwnProperty(key),意思是判断这一项是否是其自有属性,是的话才拷贝,不是就不拷贝。

也就是说__proto__上面的属性,我不拷贝。这个其实你可以根据业务需求,来决定加上和这个条件

(JQ的$.extend()是会连__proto__上的属性也拷贝下来的,但是是直接拷贝到对象上,而不是放到之前的__proto__上)

4.总结与建议

虽然大家可能经常用框架提供的api来实现深拷贝。

这篇文章分享的目的,更多还是希望用一篇文章整理清楚深浅拷贝的含义、递归实现思路,以及小伙伴们如果使用了JSON.parse()这种黑科技,一定要清楚这样写的优缺点。

5.修正

上面的deepCopy方法有漏洞,没有考虑source一开始就是数组的情况

下面是一个修改后版本

function deepCopy( source ) {let target = Array.isArray( source ) ? [] : {}for ( var k in source ) {if ( typeof source[ k ] === 'object' ) {target[ k ] = deepCopy( source[ k ] )} else {target[ k ] = source[ k ]}}return target
}

一篇文章彻底说清JS的深拷贝/浅拷贝相关推荐

  1. base64 转二进制_一篇文章弄明白Node.js与二进制数据流

    1 认识二进制数据 二进制是计算技术中广泛采用的一种数制.二进制数据是用0和1两个数码来表示的数.它的基数为2,进位规则是"逢二进一",借位规则是"借一当二", ...

  2. 二进制差分码规则_一篇文章弄明白Node.js与二进制数据流

    1 认识二进制数据 二进制是计算技术中广泛采用的一种数制.二进制数据是用0和1两个数码来表示的数.它的基数为2,进位规则是"逢二进一",借位规则是"借一当二", ...

  3. vue获取div中的值_一篇文章看懂Vue.js的11种传值通信方式

    面试的时候,也算是常考的一道题目了,而且,在日常的开发中,对于组件的封装,尤其是在 ui组件库中,会用到很多,下面,就来详细的了解下,通过这篇文章的学习,可以提升项目中组件封装的灵活性,可维护性,话不 ...

  4. 一篇文章带你搞懂微信小程序的开发过程

    点击上方"前端进阶学习交流",进行关注 回复"前端"即可获赠前端相关学习资料 今 日 鸡 汤 只解沙场为国死,何须马革裹尸还. 大家好,我进阶学习者. 前言 小 ...

  5. 一篇文章教会你创建vue项目和使用vue.js实现数据增删改查

    简介:一篇文章教会你创建vue项目和使用vue.js实现数据增删改查 [一.项目背景] 在管理员的一些后台页面里,数据列表中都会对这些数据进行增删改查的操作,例如管理员添加商品.修改商品价格.删除商品 ...

  6. list vue 添加数据方法_一篇文章教会你创建vue项目和使用vue.js实现数据增删改查...

    简介:一篇文章教会你创建vue项目和使用vue.js实现数据增删改查 [一.项目背景] 在管理员的一些后台页面里,数据列表中都会对这些数据进行增删改查的操作,例如管理员添加商品.修改商品价格.删除商品 ...

  7. js层级选择框样式_IOS和JS的交互,看这一篇文章就够了

    IOS和JS的交互,看这一篇文章就够了 创作不易,请珍惜,之后会持续更新,不断完善 Demo地址 目录 WKWebView使用.JS的交互 WKWebView使用.JS的交互 演示(本来想贴张GIF作 ...

  8. js如何获取jwt信息_谈房地产公众号如何涨粉?一篇文章让你轻松获取信息

    原标题:谈房地产公众号如何涨粉?一篇文章让你轻松获取信息 对于房地产企业来说,如何学会运营自己企业的房地产微信公众号是涨粉引流的关键一步,老话说:一步走好,步步稳.值得夸奖的是近几年中高端房地产企业在 ...

  9. 一篇文章说清Python数据分析,这个学习路线绝了

    近年来,数据分析师的需求非常大,90%的岗位技能需要掌握Python作为数据分析工具. 2021年史上最全Python数据分析学习路线,从语言基础.数据工具.商业分析.到机器学习,一篇文章帮你搞定,奥 ...

最新文章

  1. 解决删除U盘时提示无法停止‘通用卷’设备的方法
  2. python3 获取 函数参数个数 和 默认参数
  3. tensorlfow.saved_model的使用
  4. 最新综述:对话系统之用户模拟器
  5. php 快递查询,PHP查询快递信息
  6. 《复联4》| 生活需要漫威这块糖
  7. linux下的缓存机制及清理buffer/cache/swap的方法梳理
  8. 职称计算机xp系统试题,2020年职称计算机WindowsXP测试题及答案.doc
  9. ASP.NET MVC 教程学习
  10. MapTask工作机制图解
  11. 自动优化Windows Xp批处理文件
  12. cognos java_cognos与java结合 ?急!急!急!
  13. 航天飞机电子计算机象征是什么,航天飞机的特点是什么
  14. kaka的使用以及理解
  15. PV、UV、VV的意义及区别
  16. 小米手机销量是乐视的几十倍,为什么雷军还要去跟贾跃亭打口水战?
  17. 统计学中几种简单的检验方式
  18. Python-day11~13-前端初步
  19. 深入理解模型视图、自定义模型
  20. Ubuntu server关机命令

热门文章

  1. grid++中打印表格时怎么让每页有打印表头_1分钟!学会快速打印标题行,轻松搞定不加班...
  2. 基于Echarts的HTML5 Canvas折线图柱状图DEMO演示
  3. 用原生JavaScript实现淡入淡出轮播图
  4. 清除element残留表单校验
  5. Parsing error: The keyword 'const' is reservedeslint
  6. vue动态生成表单元素基础篇
  7. JavaScript数据结构与算法——队列详解(下)
  8. 详解 javascript中offsetleft属性的用法(转)
  9. Mysql多表查询笔记
  10. Python 类继承,__bases__, __mro__, super