详细分析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 相关文章推荐
AJAX的跨域与JSONP(为文章自动添加短址的功能)
Jan 17 Javascript
js实现广告漂浮效果的小例子
Jul 02 Javascript
jQuery中click事件用法实例
Dec 26 Javascript
JS实现网页滚动条感应鼠标变色的方法
Feb 26 Javascript
不想让浏览器运行javascript脚本的方法
Nov 20 Javascript
AngularJS 入门教程之HTML DOM实例详解
Jul 28 Javascript
JS实现汉字与Unicode码相互转换的方法详解
Apr 28 Javascript
使用async、enterproxy控制并发数量的方法详解
Jan 02 Javascript
Node.js事件的正确使用方法
Apr 05 Javascript
JavaScript Array对象使用方法解析
Sep 24 Javascript
浅析TypeScript 命名空间
Mar 19 Javascript
JS class语法糖的深入剖析
Jul 07 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基于curl主动推送最新内容给百度收录的方法
2016/10/14 PHP
Javascript 不能释放内存.
2006/09/07 Javascript
js调用图片隐藏&显示实现代码
2013/09/13 Javascript
FullCalendar日历插件应用之数据展现(一)
2015/12/23 Javascript
微信小程序 wx:key详细介绍
2016/10/28 Javascript
Vue.js系列之vue-router(上)(3)
2017/01/03 Javascript
浅谈React组件之性能优化
2018/03/02 Javascript
微信小程序实现tab左右切换效果
2020/11/15 Javascript
Vue.js 图标选择组件实践详解
2018/12/03 Javascript
node Buffer缓存区常见操作示例
2019/05/04 Javascript
vue实现PC端录音功能的实例代码
2019/06/05 Javascript
解决vue项目获取dom元素宽高总是不准确问题
2020/07/29 Javascript
Vue this.$router.push(参数)实现页面跳转操作
2020/09/09 Javascript
ES5和ES6中类的区别总结
2020/12/21 Javascript
Python scikit-learn 做线性回归的示例代码
2017/11/01 Python
Django项目开发中cookies和session的常用操作分析
2018/07/03 Python
python中数组和矩阵乘法及使用总结(推荐)
2019/05/18 Python
Windows10下 python3.7 安装 facenet的教程
2019/09/10 Python
Python数据可视化:箱线图多种库画法
2019/11/06 Python
Python爬取豆瓣视频信息代码实例
2019/11/16 Python
tensorflow 固定部分参数训练,只训练部分参数的实例
2020/01/20 Python
Transpose 数组行列转置的限制方式
2020/02/11 Python
python如何提取英语pdf内容并翻译
2020/03/03 Python
python由已知数组快速生成新数组的方法
2020/04/08 Python
css3实例教程 一款纯css3实现的发光屏幕旋转特效
2014/12/07 HTML / CSS
html Table 表头固定的实现
2019/01/22 HTML / CSS
详解HTML5将footer置于页面最底部的方法(CSS+JS)
2018/10/11 HTML / CSS
英国领先的电子、技术和办公用品购物网站:Ebuyer
2018/04/04 全球购物
Ibood荷兰:互联网每日最佳在线优惠
2019/02/28 全球购物
荷兰最大的多品牌男装连锁店:Adam Brandstore
2019/12/31 全球购物
什么是Deployment descriptors;都有什么类型的部署描述符
2015/07/28 面试题
四风问题个人剖析材料
2014/10/07 职场文书
就业意向协议书
2015/01/29 职场文书
2016年秋季运动会加油稿
2015/12/21 职场文书
iPhone13将有八大升级
2021/04/15 数码科技
MySQL对数据表已有表进行分区表的实现
2021/11/01 MySQL