JS 对象的深拷贝和浅拷贝
转载于原文:https://www.cnblogs.com/dabingqi/p/8502932.html
这篇文章是转载于上面的链接地址,觉得写的非常好,所以收藏了,感谢原创作者的分享。
浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用(堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,object array 则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。
浅拷贝
浅拷贝的意思就是只复制引用(指针),而未复制真正的值。
const originArray = [,,,,];
const originObj = {a:'a',b:'b',c:[,,],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();
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 = [,,,,];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false const originObj = {a:'a',b:'b',c:[,,],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false cloneObj.a = 'aa';
cloneObj.c = [,,];
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
中,有属性丢失了。。。那是为什么呢?
undefined
、function
、symbol
会在转换过程中被忽略。。。
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).
明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝
递归的方法
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值
的操作,简单粗暴上代码:
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:[,,],d:{dd:'dd'}};
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false cloneObj.a = 'aa';
cloneObj.c = [,,];
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 = [,,,,];
const cloneArray = originArray.concat(); console.log(cloneArray === originArray); // false
cloneArray.push(); // [1,2,3,4,5,6]
console.log(originArray); [,,,,];
看上去是深拷贝的。
我们来考虑一个问题,如果这个对象是多层的,会怎样。
const originArray = [,[,,],{a:}];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray[].push();
cloneArray[].a = ;
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 = [,,,,];
const cloneArray = originArray.slice();
console.log(cloneArray === originArray); //false
cloneArray.push(); // [1,2,3,4,5,6]
console.log(originArray); [,,,,];
同样地,我们试试多层的数组。
const originArray = [,[,,],{a:}];
const cloneArray = originArray.slice();
console.log(cloneArray === originArray); // false
cloneArray[].push();
cloneArray[].a = ;
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 obj
结论:Object.assign()
拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
... 展开运算符
const originArray = [,,,,,[,,]];
const originObj = {a:,b:{bb:}}; const cloneArray = [...originArray];
cloneArray[] = ;
cloneArray[].push();
console.log(originArray); // [1,2,3,4,5,[6,7,8,9]] const cloneObj = {...originObj};
cloneObj.a = ;
cloneObj.b.bb = ;
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:[,,],d:{dd:'dd'}};
const cloneObj = shallowClone(originObj);
console.log(cloneObj === originObj); // false
cloneObj.a='aa';
cloneObj.c=[,,];
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
实现的是深拷贝,但是对目标对象有要求(非 undefined,function);- 若想真正意义上的深拷贝,请递归。
JS 对象的深拷贝和浅拷贝的更多相关文章
- JS对象复制(深拷贝、浅拷贝)
如何在 JS 中复制对象 在本文中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个方面,介绍多种 JS 中复制对象的方法. 在开始之前,有一些基础知识值得一提:Javas ...
- js中的深拷贝与浅拷贝
对象的深拷贝于浅拷贝 对于基本类型,浅拷贝过程就是对值的复制,这个过程会开辟出一个新的内存空间,将值复制到新的内存空间.而对于引用类型来书,浅拷贝过程就是对指针的复制,这个过程并没有开辟新的堆内存空间 ...
- java 复制Map对象(深拷贝与浅拷贝)
java 复制Map对象(深拷贝与浅拷贝) CreationTime--2018年6月4日10点00分 Author:Marydon 1.深拷贝与浅拷贝 浅拷贝:只复制对象的引用,两个引用仍然指向 ...
- 谈谈java中对象的深拷贝与浅拷贝
知识点:java中关于Object.clone方法,对象的深拷贝与浅拷贝 引言: 在一些场景中,我们需要获取到一个对象的拷贝,这时候就可以用java中的Object.clone方法进行对象的复制,得到 ...
- js 中数组或者对象的深拷贝和浅拷贝
浅拷贝 : 就是两个js 对象指向同一块内存地址,所以当obj1 ,obj2指向obj3的时候,一旦其中一个改变,其他的便会改变! 深拷贝:就是重新复制一块内存,这样就不会互相影响. 有些时候我们定义 ...
- 探究JS中对象的深拷贝和浅拷贝
深拷贝和浅拷贝的区别 在讲深拷贝和浅拷贝的区别之前,回想一下我们平时拷贝一个对象时是怎么操作的?是不是像这样? var testObj1 = {a: 1, b:2}, testObj2=testObj ...
- 一篇文章理解JS数据类型、深拷贝和浅拷贝
前言 笔者最近整理了一些前端技术文章,如果有兴趣可以参考这里:muwoo blogs.接下来我们进入正片: js 数据类型 六种 基本数据类型: Boolean. 布尔值,true 和 false. ...
- js 中的深拷贝与浅拷贝
在面试中经常会问到js的深拷贝和浅拷贝,也常常让我们手写,下面我们彻底搞懂js的深拷贝与浅拷贝. 在js中 Array 和 Object 这种引用类型的值,当把一个变量赋值给另一个变量时,这个值得副 ...
- js中的深拷贝和浅拷贝2
所谓 深浅拷贝: 对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝. 而如果是在堆中重新分配内存,拥有不同的地址,但是值是一 ...
随机推荐
- json与xml数据输出类
class Response { /** * 按json方式输出通信数据 * @param integer $code 状态码 * @param string $message 提示信息 * @par ...
- python:爬虫入门
直接上代码吧: 一.爬取某外卖平台的指定商家菜品信息 from urllib import request import json import random url = "https:// ...
- ios 应用发布渠道大全
#91助手 #同步推 #威锋网 #力美 #PP助手 #同步推线下卖场 #AppleTree #快用苹果助手 #AppleTree-广告 #AppleTree-新市场 #魔品助手ios #itools ...
- 版本控制-GitHub
前面几篇文章,我们介绍了Git的基本用法及Git服务器的搭建,本篇文章来学习一下如何使用GitHub.GitHub是开源的代码库以及版本控制库,是目前使用网络上使用最为广泛的服务,GitHub可以托管 ...
- 转 c#中stringbuilder的使用
String 对象是不可改变的.每次使用 System.String 类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间.在需要对字符串执行重复修改的情况 ...
- alter日志报WARNING: too many parse errors
数据库版本:12.2.0 操作系统版本:RHEL7.2 最近观察到一个数据库alert日志老是报硬解析太多错误,且对应的sql语句都是查看数据字典表: 2017-06-16T08:46:46.4174 ...
- Effective Java 第三版——55. 明智而审慎地返回Optional
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- 物联网架构成长之路(3)-EMQ消息服务器了解
1. 了解 物联网最基础的就是通信了.通信协议,物联网协议好像有那么几个,以前各个协议都有优劣,最近一段时间,好像各大厂商都采用MQTT协议,所以我也不例外,不搞特殊,采用MQTT协议,选定了协议,接 ...
- emacs自动折行设置
- emacs自动折行 - 临时设置下 M-x `toggle-truncate-lines` - init.el 中添加 `(toggle-truncate-lines 1)`
- java框架篇---hibernate(一对多)映射关系
一对多关系可以分为单向和双向. 一对多关系单向 单向就是只能从一方找到另一方,通常是从主控类找到拥有外键的类(表).比如一个母亲可以有多个孩子,并且孩子有母亲的主键作为外键.母亲与孩子的关系就是一对多 ...