详细分析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 left,right,mid函数
Jun 10 Javascript
jquery随意添加移除html的实现代码
Jun 21 Javascript
JS获取iframe中longdesc属性的方法
Apr 01 Javascript
详解参数传递四种形式
Jul 21 Javascript
js实现仿爱微网两级导航菜单效果代码
Aug 31 Javascript
jquery UI Datepicker时间控件的使用方法(加强版)
Nov 07 Javascript
解决angular的post请求后SpringMVC后台接收不到参数值问题的方法
Dec 10 Javascript
微信小程序 网络请求(GET请求)详解
Nov 16 Javascript
使用vue + less 实现简单换肤功能的示例
Feb 21 Javascript
微信小程序自定义组件的实现方法及自定义组件与页面间的数据传递问题
Oct 09 Javascript
Nuxt的路由配置和参数传递方式
Nov 06 Javascript
javascript之Object.assign()的痛点分析
Mar 03 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 array数组的教程详解
2013/06/05 PHP
PHP使用MPDF类生成PDF的方法
2015/12/08 PHP
phalcon框架使用指南
2016/02/23 PHP
PHP基于ip2long实现IP转换整形
2020/12/11 PHP
JavaScript 图片预览效果 推荐
2009/12/22 Javascript
js内存泄露的几种情况详细探讨
2013/05/31 Javascript
用javascript判断IE版本号简单实用且向后兼容
2013/09/11 Javascript
javascript使用onclick事件改变选中行的颜色
2013/12/30 Javascript
JS生成随机字符串的多种方法
2014/06/10 Javascript
js实现模拟计算器退格键删除文字效果的方法
2015/05/07 Javascript
详解Angular开发中的登陆与身份验证
2016/07/27 Javascript
jQuery插件easyUI实现通过JS显示Dialog的方法
2016/09/16 Javascript
利用浮层使select不可选的实现方法
2016/12/03 Javascript
微信小程序 less文件编译成wxss文件实现办法
2016/12/05 Javascript
jquery,js简单实现类似Angular.js双向绑定
2017/01/13 Javascript
Vue.js仿Metronic高级表格(二)数据渲染
2017/04/19 Javascript
JS实现禁止用户使用Ctrl+鼠标滚轮缩放网页的方法
2017/04/28 Javascript
Vue 实现从小到大的横向滑动效果详解
2019/10/16 Javascript
ES2020让代码更优美的运算符 (?.) (??)
2021/01/04 Javascript
[46:14]VGJ.T vs Liquid 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
python登陆asp网站页面的实现代码
2015/01/14 Python
python3结合openpyxl库实现excel操作的实例代码
2018/09/11 Python
python paramiko利用sftp上传目录到远程的实例
2019/01/03 Python
Flask框架模板继承实现方法分析
2019/07/31 Python
Python sqlite3查询操作过程解析
2020/02/20 Python
pycharm 2018 激活码及破解补丁激活方式
2020/09/21 Python
pytorch读取图像数据转成opencv格式实例
2020/06/02 Python
最简单的matplotlib安装教程(小白)
2020/07/28 Python
python中pickle模块浅析
2020/12/29 Python
HTML5 Plus 实现手机APP拍照或相册选择图片上传功能
2016/07/13 HTML / CSS
澳大利亚新奇小玩意网站:Yellow Octopus
2017/12/28 全球购物
Bluebella法国官网:英国性感内衣品牌
2019/05/03 全球购物
协商一致解除劳动合同协议书
2014/09/14 职场文书
学生个人评语大全
2015/01/04 职场文书
mybatis调用sqlserver存储过程返回结果集的方法
2021/05/08 SQL Server
Tomcat安装使用及部署Web项目的3种方法汇总
2022/08/14 Servers