JavaScript基础心法——深拷贝和浅拷贝
浅拷贝和深拷贝都是对于 JS 中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。
前言
说到深浅拷贝,必须先提到的是JavaScript的数据类型,之前的一篇文章JavaScript基础心法——数据类型说的很清楚了,这里就不多说了。
需要知道的就是一点:JavaScript的数据类型分为基本数据类型和引用数据类型。
对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的。
浅拷贝
浅拷贝的意思就是只复制引用,而未复制真正的值。
const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneArray = originArray;
const cloneObj = originObj;
console.log(cloneArray); // [1,2,3,4,5]
console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}
cloneArray.push(6);
cloneObj.a = {aa:'aa'};
console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]
console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
上面的代码是最简单的利用 =
赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 cloneArray
和 cloneObj
改变,originArray
和 originObj
也随着发生了变化。
深拷贝
深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
目前实现深拷贝的方法不多,主要是两种:
-
利用 JSON
对象中的parse
和stringify
-
利用递归来实现每一层都重新创建对象并赋值
JSON.stringify/parse的方法
先看看这两个方法吧:
The JSON.stringify() method converts a JavaScript value to a JSON string.
JSON.stringify
是将一个 JavaScript
值转成一个 JSON
字符串。
The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.
JSON.parse
是将一个 JSON
字符串转成一个 JavaScript
值或对象。
很好理解吧,就是 JavaScript
值和 JSON
字符串的相互转换。
它能实现深拷贝呢?我们来试试。
const originArray = [1,2,3,4,5];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
cloneObj.a = 'aa';
cloneObj.c = [1,1,1];
cloneObj.d.dd = 'doubled';
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
确实是深拷贝,也很方便。但是,这个方法只能适用于一些简单的情况。比如下面这样的一个对象就不适用:
const originObj = {
name:'axuebin',
sayHello:function(){
console.log('Hello World');
}
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"}
发现在 cloneObj
中,有属性丢失了。。。那是为什么呢?
在 MDN
上找到了原因:
If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
undefined
、function
、symbol
会在转换过程中被忽略。。。
明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝。
递归的方法
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值
的操作,简单粗暴上代码:
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
我们来试试:
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false
cloneObj.a = 'aa';
cloneObj.c = [1,1,1];
cloneObj.d.dd = 'doubled';
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
可以。那再试试带有函数的:
const originObj = {
name:'axuebin',
sayHello:function(){
console.log('Hello World');
}
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = deepClone(originObj);
console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}
也可以。搞定。
是不是以为这样就完了??当然不是。
JavaScript中的拷贝方法
我们知道在 JavaScript
中,数组有两个方法 concat
和 slice
是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。
同时,ES6 中 引入了 Object.assgn
方法和 ...
展开运算符也能实现对对象的拷贝。
那它们是浅拷贝还是深拷贝呢?
concat
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。
看着这意思,很像是深拷贝啊,我们来试试:
const originArray = [1,2,3,4,5];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray.push(6); // [1,2,3,4,5,6]
console.log(originArray); [1,2,3,4,5];
看上去是深拷贝的。
我们来考虑一个问题,如果这个对象是多层的,会怎样。
const originArray = [1,[1,2,3],{a:1}];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray[1].push(4);
cloneArray[2].a = 2;
console.log(originArray); // [1,[1,2,3,4],{a:2}]
originArray
中含有数组 [1,2,3]
和对象 {a:1}
,如果我们直接修改数组和对象,不会影响 originArray
,但是我们修改数组 [1,2,3]
或对象 {a:1}
时,发现 originArray
也发生了变化。
结论:concat
只是对数组的第一层进行深拷贝。
slice
The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
解释中都直接写道是 a shallow copy
了 ~
但是,并不是!
const originArray = [1,2,3,4,5];
const cloneArray = originArray.slice();
console.log(cloneArray === originArray); // false
cloneArray.push(6); // [1,2,3,4,5,6]
console.log(originArray); [1,2,3,4,5];
同样地,我们试试多层的数组。
const originArray = [1,[1,2,3],{a:1}];
const cloneArray = originArray.slice();
console.log(cloneArray === originArray); // false
cloneArray[1].push(4);
cloneArray[2].a = 2;
console.log(originArray); // [1,[1,2,3,4],{a:2}]
果然,结果和 concat
是一样的。
结论:slice
只是对数组的第一层进行深拷贝。
Object.assign()
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
复制复制复制。
那到底是浅拷贝还是深拷贝呢?
自己试试吧。。
结论:Object.assign()
拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
... 展开运算符
const originArray = [1,2,3,4,5,[6,7,8]];
const originObj = {a:1,b:{bb:1}};
const cloneArray = [...originArray];
cloneArray[0] = 0;
cloneArray[5].push(9);
console.log(originArray); // [1,2,3,4,5,[6,7,8,9]]
const cloneObj = {...originObj};
cloneObj.a = 2;
cloneObj.b.bb = 2;
console.log(originObj); // {a:1,b:{bb:2}}
结论:...
实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
首层浅拷贝
我们知道了,会有一种情况,就是对目标对象的第一层进行深拷贝,然后后面的是浅拷贝,可以称作“首层浅拷贝”。
我们可以自己实现一个这样的函数:
function shallowClone(source) {
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for (let keys in source) { // 遍历目标
if (source.hasOwnProperty(keys)) {
targetObj[keys] = source[keys];
}
}
return targetObj;
}
我们来测试一下:
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = shallowClone(originObj);
console.log(cloneObj === originObj); // false
cloneObj.a='aa';
cloneObj.c=[1,1,1];
cloneObj.d.dd='surprise';
经过上面的修改,cloneObj
不用说,肯定是 {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}}
了,那 originObj
呢?刚刚我们验证了 cloneObj === originObj
是 false
,说明这两个对象引用地址不同啊,那应该就是修改了 cloneObj
并不影响 originObj
。
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}}
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'surprise'}}
What happend?
originObj
中关于 a
、c
都没被影响,但是 d
中的一个对象被修改了。。。说好的深拷贝呢?不是引用地址都不一样了吗?
原来是这样:
-
从 shallowClone
的代码中我们可以看出,我们只对第一层的目标进行了深拷贝
,而第二层开始的目标我们是直接利用=
赋值操作符进行拷贝的。 -
so,第二层后的目标都只是复制了一个引用,也就是浅拷贝。
总结
-
赋值运算符 =
实现的是浅拷贝,只拷贝对象的引用值; -
JavaScript 数组和对象自带的拷贝方法都是“首层浅拷贝”; -
JSON.stringify
实现的是深拷贝,但是对目标对象有要求; -
若想真正意义上的深拷贝,请递归。
本文分享自微信公众号 - 前端试炼(code-photo)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
DevSecOps在百度的实践
本文将从传统SDL开始,介绍百度从SDL到DevSecOps的演进历程。全文涉及SDL的痛点、DevSecOps建设初衷、实践形式、落地思路,以及落地后的效果与收益,也会介绍DevSecOps在云原生时代的探索思路与落地场景。如果你正准备或者已经参与到企业DevSecOps建设的相关工作中,这篇文章或许能够给你的工作带来一些启发。 一、轻量级SDL,百度的前DevSecOps时代 作为一家大型互联网公司,百度具备着所有大型公司和互联网公司的典型特点,业务体系繁多、业务数量庞大,业务迭代迅速。 在百度内部,业务研发模式有别于传统的SDLC模型,更接近于DevOps模式,CI/CD工具集成和自动化程度高,产品迭代频次多、周期短。 面对这样的业务研发场景,传统通过输出人力到业务团队,全流程跟进和解决业务研发生命周期的安全问题的方式已经不再适合。安全团队不能,也不可能将人力覆盖到所有业务。因此,安全团队势必需要构建通用性的产品安全基础设施,将其嵌入到产品研发流程中,然后配合重点业务的小范围安全评估,来实现高可用、高自动化的软件研发生命周期安全保障。 在百度,我们将这种方式称之为轻量级SDL,即...
- 下一篇
支付宝研究员王益:Go+可有效补全Python的不足
王益,蚂蚁集团研究员,开源项目SQLFlow 和 ElasticDL 的负责人。他从10岁开始写代码。曾经用自己焊接的电路板扩展“中华学习机”来把自家的老式“威力牌”双筒洗衣机改造成了自动洗衣机;用Apple BASIC语言和6502汇编混合编程写了人生中第一个游戏;高中自学了大学所有计算机课程,参加计算机水平测试,先后获得了“程序员”、“高级程序员”、“系统分析员”认证。王益从事 AI 基础架构工作十三年,先后在全球多家顶级互联网公司任职,亦曾在硅谷和北京两地创业。 不久前许式伟(江湖人称老许)的 Go+ 项目在 Hacker News 上掀起了一阵风潮[1]。我一见倾心,参与贡献。最近老许和社区组织了一个视频交流,拉我跟大家说说为啥关注 Go+ 以及图个啥。在直播交流后,根据弹幕反馈,以及两位好友——洪明胜(TensorFlow Runtime 负责人)以及王玉(沈雕墨)的建议,做了修改。 我做分布式深度学习系统十三年了,尤其是 2016 年徐伟老师让我接替他作为他原创的 PaddlePaddle 项目的负责人之后,在工业系统中对 Python 的亲身体会让我对其局限了解愈深。...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装