详细分析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下判断一个对象是否具有指定名称的属性的的代码
Jan 11 Javascript
javascript 四则运算精度修正函数代码
May 31 Javascript
JQuery设置和去除disabled属性的5种方法总结
May 16 Javascript
js取消单选按钮选中并判断对象是否为空
Nov 14 Javascript
node.js中的buffer.Buffer.isBuffer方法使用说明
Dec 14 Javascript
JavaScript实现获取dom中class的方法
Feb 09 Javascript
JavaScript 模块化编程(笔记)
Apr 08 Javascript
JS操作COOKIE实现备忘记录的方法
Apr 01 Javascript
jQuery中弹出iframe内嵌页面元素到父页面并全屏化的实例代码
Dec 27 Javascript
JS数组去重的6种方法完整实例
Dec 08 Javascript
详解vue中使用微信jssdk
Apr 19 Javascript
jQuery+ajax实现文件上传功能
Dec 22 jQuery
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编写大型网站问题集
2007/03/06 PHP
两个强悍的php 图像处理类1
2009/06/15 PHP
关于PHPDocument 代码注释规范的总结
2013/06/25 PHP
PHP使用mysql_fetch_object从查询结果中获取对象集的方法
2015/03/18 PHP
php array_pop 删除数组最后一个元素实例
2016/11/02 PHP
PHP实现常用排序算法的方法
2020/02/05 PHP
Javascript 网页水印(非图片水印)实现代码
2010/03/01 Javascript
Javascript的并行运算实现代码
2010/11/19 Javascript
Javascript实现的类似Google的Div拖动效果代码
2011/08/09 Javascript
jquery阻止冒泡事件使用模拟事件
2013/09/06 Javascript
浅谈document.write()输出样式
2015/05/07 Javascript
JavaScript中用sort()方法对数组元素进行排序的操作
2015/06/09 Javascript
详解JavaScript基于面向对象之创建对象(2)
2015/12/10 Javascript
使用PHP+JavaScript将HTML页面转换为图片的实例分享
2016/04/18 Javascript
原生js开发的日历插件
2017/02/04 Javascript
jQuery实现单击按钮遮罩弹出对话框效果(1)
2017/02/20 Javascript
vue移动端路由切换实例分析
2018/05/14 Javascript
解决select2在bootstrap modal中不能正常使用的问题
2018/08/09 Javascript
jQuery+css last-child实现选择最后一个子元素操作示例
2018/12/10 jQuery
基于Proxy的小程序状态管理实现
2019/06/14 Javascript
vue实现下拉菜单树
2020/10/22 Javascript
进一步探究Python的装饰器的运用
2015/05/05 Python
实例讲解Python编程中@property装饰器的用法
2016/06/20 Python
Python正则捕获操作示例
2017/08/19 Python
R语言 vs Python对比:数据分析哪家强?
2017/11/17 Python
Python后台开发Django会话控制的实现
2019/04/15 Python
Django项目主urls导入应用中views的红线问题解决
2019/08/10 Python
Django中ajax发送post请求 报403错误CSRF验证失败解决方案
2019/08/13 Python
Pytorch自定义Dataset和DataLoader去除不存在和空数据的操作
2021/03/03 Python
使用phonegap获取位置信息的实现方法
2017/03/31 HTML / CSS
机械设计及其自动化专业推荐信
2013/10/31 职场文书
工商干部先进事迹
2014/05/14 职场文书
2015年党风建设工作总结
2015/04/29 职场文书
第一节英语课开场白
2015/06/01 职场文书
《风筝》教学反思
2016/02/23 职场文书
Python合并pdf文件的工具
2021/07/01 Python