详细分析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 相关文章推荐
扩展JavaScript功能的正确方法(译文)
Apr 12 Javascript
jquery入门—数据删除与隔行变色以及图片预览
Jan 07 Javascript
JavaScript字符串对象replace方法实例(用于字符串替换或正则替换)
Oct 16 Javascript
跟我学习javascript的Date对象
Nov 19 Javascript
JavaScript截取指定长度字符串点击可以展开全部代码
Dec 04 Javascript
SpringMVC restful 注解之@RequestBody进行json与object转换
Dec 10 Javascript
解决vue中修改了数据但视图无法更新的情况
Aug 27 Javascript
JS集合set类的实现与使用方法示例
Feb 01 Javascript
vue中利用Promise封装jsonp并调取数据
Jun 18 Javascript
vue中实现Monaco Editor自定义提示功能
Jul 05 Javascript
基于layui table返回的值的多级嵌套的解决方法
Sep 19 Javascript
基于JavaScript获取base64图片大小
Oct 18 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
php面向对象全攻略 (十二) 抽象方法和抽象类
2009/09/30 PHP
Look And Say 序列php实现代码
2011/05/22 PHP
apache php模块整合操作指南
2012/11/16 PHP
ThinkPHP自动填充实现无限级分类的方法
2014/08/22 PHP
Codeigniter检测表单post数据的方法
2015/03/21 PHP
php学习笔记之mb_strstr的基本使用
2018/02/03 PHP
Laravel框架实现的使用smtp发送邮件功能示例
2019/03/12 PHP
用jQuery简化JavaScript开发分析
2009/02/19 Javascript
JS打开图片另存为对话框实现代码
2012/12/26 Javascript
JS中三目运算符和if else的区别分析与示例
2014/11/21 Javascript
详解Node.Js如何处理post数据
2016/09/19 Javascript
利用HTML5+Socket.io实现摇一摇控制PC端歌曲切换
2017/01/13 Javascript
微信小程序 弹幕功能简单实例
2017/02/14 Javascript
详解Vue.js入门环境搭建
2017/03/17 Javascript
解析NodeJS异步I/O的实现
2017/04/13 NodeJs
原生js二级联动效果
2017/06/20 Javascript
简单谈谈React中的路由系统
2017/07/25 Javascript
JS实现的抛物线运动效果示例
2018/01/30 Javascript
在vue项目中引用Iview的方法
2018/09/14 Javascript
[03:40]2014DOTA2国际邀请赛 B神专访:躲箭真的很难
2014/07/13 DOTA
Python 字符串操作实现代码(截取/替换/查找/分割)
2013/06/08 Python
IntelliJ IDEA安装运行python插件方法
2018/12/10 Python
pycharm显示远程图片的实现
2019/11/04 Python
Pycharm pyuic5实现将ui文件转为py文件,让UI界面成功显示
2020/04/08 Python
详解用Python调用百度地图正/逆地理编码API
2020/07/02 Python
Anaconda的安装与虚拟环境建立
2020/11/18 Python
Python 实现RSA加解密文本文件
2020/12/30 Python
美国皮靴公司自1863年:The Frye Company
2016/11/30 全球购物
美国巧克力喷泉品牌:Sephra
2019/05/05 全球购物
C语言如何决定使用那种整数类型
2016/11/26 面试题
社团文化节策划书
2014/02/01 职场文书
垃圾分类的活动方案
2014/08/15 职场文书
创先争优公开承诺书
2014/08/30 职场文书
2015年党员公开承诺事项
2015/04/27 职场文书
供应商食品安全承诺书
2015/04/29 职场文书
pandas中DataFrame数据合并连接(merge、join、concat)
2021/05/30 Python