详细分析JavaScript中的深浅拷贝


Posted in Javascript onSeptember 17, 2020

在说JS中深浅拷贝之前,我们需要对JS中的数据类型有所了解,分为基本数据类型与引用数据类型,对于基本数据类型并没有深浅拷贝的说法,深浅拷贝主要针对引用数据类型。

一、浅拷贝

浅拷贝只复制了引用,并没有复制值。在JS中最简单的浅拷贝就是利用“=”赋值操作符来实现。

var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
  fun:function(){
    console.log('fun')
  }
}
var obj2 = obj1
obj2.a = 666 /* 修改obj2的值,obj1的值也随之改变 */
console.log(obj1) /* {a: 666, b: Array(3), c: {…}, fun: ƒ} */

上述代码中,我们修改obj2的值,obj1的值也随之发生了改变,使用”=“只实现了浅拷贝。

二、深拷贝

深拷贝是对目标的完全拷贝,进行深拷贝后的两个值互不影响。

1. 利用JSON.stringify与JSON.parse方法

JSON.stringify将一个JavaScript值转换为JSON字符串;

JSON.parse将一个JSON字符串转换为JavaScript值。

var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
}			
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 12
console.log(obj1) /* {a: 1, b: Array(3), c: {…}} */

修改obj2的值并没有影响到obj1中的属性值,显然,我们利用JSON.parse与JSON.stringify实现了深拷贝。

但是,真的可以这么简单的实现吗?我们来看看下面的例子!

var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
  fun:function(){
    console.log('fun')
  }
}			
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 12
console.log(obj1) /* {a: 1, b: Array(3), c: {…}, fun: ƒ} */
console.log(obj2) /* {a: 12, b: Array(3), c: {…}} */

转换后的obj2中没有了fun这个属性,这是由于在利用JSON.stringify转换过程中,忽略了undefined、function、symbol。显然,当我们的对象中出现这些类型的属性时无法利用该方法实现深拷贝。

2. 递归

function deepClone(source){
  if(!isObject(source)) return source
  var newObj = source instanceof Array? []:{}
  for(let key in source){
	if(source.hasOwnProperty(key)){
	  newObj[key] = isObject(source[key])?deepClone(source[key]):source[key]
    }
  }
  return newObj
}
function isObject(x) {
  return typeof x === 'object' && x != null
}

测试一下上述方法:

var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
  fun:function(){
	console.log('fun')
  }
}			
var obj2 = deepClone(obj1)
obj2.a = 12
console.log(obj1) /* {a: 1, b: Array(3), c: {…}, fun: ƒ} */

通过例子可以看到,我们修改了obj2中a属性的值,但是并没有影响到obj1中的a属性值。通过递归我们可以实现深拷贝!

注意:上述方法未解决循环引用的问题。

var obj1 = {}
obj1.a = obj1
var obj2 = deepClone(obj1) /* 报错,栈溢出 */
console.log(obj2)

关于如何解决循环引用问题以及实现Symbol类型拷贝,稍后完善。

三、其他拷贝方法

1. 数组中的concat()和slice()方法

我们知道数组中有两个方法concat和slice可以完成复制数组,并且返回新数组。以concat为例。

var arr = [1,2,3]
var arr2 = arr.concat()
arr2[2]=4
console.log(arr) /* [1, 2, 3] */
console.log(arr2) /* [1, 2, 4] */

改变arr2的值,并没有影响到arr的值,这是实现了数组的深拷贝吗,先不急于下结论,一起看看下面的例子再来分析:

var arr = [1,2,3,[4,5,6],{a:7}]
var arr2 = arr.concat()
arr2[3] = 444
arr2[4].a=8
console.log(arr) /* [1,2,3,[4,5,6],{a:8}] */
console.log(arr2) /* [1,2,3,444,{a:8}] */

我们直接修改arr2[3],并没有引起arr的改变,但是我们修改arr2[4].a时,arr中的相应元素跟着一起发生了改变。其实,我们对arr2数组中的元素直接进行改变(比如:arr2[0]=***,arr2[1]=***,arr2[3]=***)时,不会影响到原数组arr,但是我们修改数组中的[3,4,5]或{a:7}时,会造成原数组arr的改变。

结论:concat()方法对数组第一层进行了深拷贝。

可以再试试数组的slice()方法,它也是只对数组第一层进行了深拷贝。

2. Object.assign()和...展开运算符

var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'}
}
var obj2 = {...obj1}
obj2.a = 666
obj2.c.name = 'xinxin'
console.log(obj1) /* {a:1,b:[2,3,4],c:{name:'xinxin'}} */

可以看到利用...展开运算符实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。

可以试试Object.assign()方法:

var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'}
}
var obj2 = {}
Object.assign(obj2,obj1)
obj2.a = 666
obj2.b[0] = 0
console.log(obj1) /* {a:1,b:[0,3,4],c:{name:'tanj'} */

同样,只对对象第一层进行了深拷贝,假如源对象的属性值(例如obj1)是一个指向对象的引用,obj2也只拷贝那个引用值。所以改变obj2中b所指向的那个数组时,obj1的值也会发生改变。

我们可以自己实现一个这样的效果,只对第一层进行深拷贝:

function shallowClone(source) {
  const newObj = source.constructor === Array ? [] : {}
  for (let keys in source) {
    if (source.hasOwnProperty(keys)) {
	  newObj[keys] = source[keys]
	}
  }
  return newObj
}

以上就是分析JavaScript中的深浅拷贝的详细内容,更多关于JavaScript 深浅拷贝的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
用js实现控制内容的向上向下滚动效果
Jun 26 Javascript
jquery中对表单的基本操作代码
Jul 29 Javascript
JQuery中根据属性或属性值获得元素(6种情况获取方法)
Jan 17 Javascript
解决jquery1.9不支持browser对象的问题
Nov 13 Javascript
javascript引用类型指针的工作方式
Apr 13 Javascript
Angular.JS学习之依赖注入$injector详析
Oct 20 Javascript
12个非常有用的JavaScript技巧
May 17 Javascript
使用Node.js搭建静态资源服务详细教程
Aug 02 Javascript
一步步教你利用webpack如何搭一个vue脚手架(超详细讲解和注释)
Jan 08 Javascript
解决vue-cli项目开发运行时内存暴涨卡死电脑问题
Oct 29 Javascript
VUE+elementui面包屑实现动态路由详解
Nov 04 Javascript
原生JS生成指定位数的验证码
Oct 28 Javascript
js实现鼠标滑动到某个div禁止滚动
Sep 17 #Javascript
原生js+css实现tab切换功能
Sep 17 #Javascript
vue使用screenfull插件实现全屏功能
Sep 17 #Javascript
Vue使用screenfull实现全屏效果
Sep 17 #Javascript
详解JavaScript中的数据类型,以及检测数据类型的方法
Sep 17 #Javascript
JavaScript编码小技巧分享
Sep 17 #Javascript
如何利用node转发请求详解
Sep 17 #Javascript
You might like
桌面中心(三)修改数据库
2006/10/09 PHP
探讨:array2xml和xml2array以及xml与array的互相转化
2013/06/24 PHP
php实现与python进行socket通信的方法示例
2017/08/30 PHP
php实现统计二进制中1的个数算法示例
2018/01/23 PHP
一个分享按钮的插件使用介绍(可扩展,内附开发制作流程)
2011/09/19 Javascript
jQuery EasyUI API 中文文档 - NumberBox数字框
2011/10/13 Javascript
nodejs的require模块(文件模块/核心模块)及路径介绍
2013/01/14 NodeJs
AJAX跨域请求json数据的实现方法
2013/11/11 Javascript
jQuery圆形统计图开发实例
2015/01/04 Javascript
js数组如何添加json数据及js数组与json的区别
2015/10/27 Javascript
Perl Substr()函数及函数的应用
2015/12/16 Javascript
javascript新闻跑马灯实例代码
2020/07/29 Javascript
easyui-datagrid特殊字符不能显示的处理方法
2017/04/12 Javascript
js实现添加删除表格(两种方法)
2017/04/27 Javascript
值得收藏的vuejs安装教程
2017/11/21 Javascript
(模仿京东用户注册)用JQuery实现简单表单验证,初学者必看
2018/01/08 jQuery
JS与SQL方式随机生成高强度密码示例
2018/12/29 Javascript
微信小程序云开发 搭建一个管理小程序
2019/05/17 Javascript
[46:42]DOTA2-DPC中国联赛正赛 Aster vs Magma BO3 第二场 3月5日
2021/03/11 DOTA
Python实现代码统计工具(终极篇)
2016/07/04 Python
PyCharm安装第三方库如Requests的图文教程
2018/05/18 Python
Pycharm 实现下一个文件引用另外一个文件的方法
2019/01/17 Python
对django中foreignkey的简单使用详解
2019/07/28 Python
python爬虫添加请求头代码实例
2019/12/28 Python
使用Keras 实现查看model weights .h5 文件的内容
2020/06/09 Python
Python爬虫如何应对Cloudflare邮箱加密
2020/06/24 Python
Python 远程开关机的方法
2020/11/18 Python
详解numpy1.19.4与python3.9版本冲突解决
2020/12/15 Python
谈谈对css属性box-sizing的了解
2017/01/04 HTML / CSS
吉尔德利巧克力公司:Ghirardelli Chocolate Company
2019/03/27 全球购物
材料采购员岗位职责
2013/12/17 职场文书
旷课检讨书1000字
2014/02/14 职场文书
安全生产活动月方案
2014/03/09 职场文书
项目申请汇报材料
2014/08/16 职场文书
租房安全协议书
2014/08/20 职场文书
行政处罚事先告知书
2015/07/01 职场文书