整理自互联网 整理作随笔 若有类似纯属抄袭javascript
浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用(堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,object array 则是存入桟中,只用一个指针来引用值),若是拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。java
浅拷贝的意思就是只复制引用(指针),而未复制真正的值。数组
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
也随着发生了变化。app
深拷贝就是对目标的彻底拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。函数
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。测试
目前实现深拷贝的方法很少,主要是两种:spa
JSON
对象中的 parse
和 stringify
先看看这两个方法吧:指针
The JSON.stringify() method converts a JavaScript value to a JSON string.
JSON.stringify
是将一个 JavaScript
值转成一个 JSON
字符串。code
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
中,有属性丢失了。。。那是为何呢?
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:[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
中,数组有两个方法 concat
和 slice
是能够实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。
同时,ES6 中 引入了 Object.assgn
方法和 ...
展开运算符也能实现对对象的拷贝。
那它们是浅拷贝仍是深拷贝呢?
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
只是对数组的第一层进行深拷贝。
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
只是对数组的第一层进行深拷贝。
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 = [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
的代码中咱们能够看出,咱们只对第一层的目标进行了 深拷贝
,而第二层开始的目标咱们是直接利用 =
赋值操做符进行拷贝的。=
实现的是浅拷贝,只拷贝对象的引用值;JSON.stringify
实现的是深拷贝,可是对目标对象有要求(非 undefined,function);