이번에는 JS의 깊고 얕은 복사 사용에 대해 자세히 설명하겠습니다. JS 깊고 얕은 복사 사용 시 주의사항은 무엇인가요?
서문
깊은 복사와 얕은 복사에 관해 먼저 JavaScript의 데이터 유형을 언급해야 합니다. 이전 기사 JavaScript의 기본 - 데이터 유형에서 매우 명확하게 설명했기 때문에 여기서는 자세히 설명하지 않겠습니다.
당신이 알아야 할 것은 한 가지입니다: JavaScript 데이터 유형은 기본 데이터 유형과 참조 데이터 유형으로 구분됩니다.
기본 데이터 유형의 복사본의 경우 다크 복사본과 얕은 복사본 사이에 차이가 없습니다. 우리가 다크 복사본과 얕은 복사본이라고 부르는 것은 참조 데이터 유형에 대한 것입니다.
Shallow copy
Shallow copy는 참조만 복사되고 실제 값은 복사되지 않는다는 의미입니다.
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
도 변경되었습니다. 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
딥 카피
딥 카피는 대상의 전체 복사본입니다. 참조 레이어만 복사하는 얕은 복사본과 달리 값까지 복사됩니다.
딥 카피가 만들어지는 한, 그들은 서로 상호 작용하지 않으며 누구도 서로에게 영향을 미치지 않습니다. 현재 전체 복사를 구현하는 방법은 많지 않으며 주로 두 가지 방법이 있습니다:각 수준에서 값을 할당
먼저 이 두 가지 메소드를 살펴보겠습니다.
🎜JSON.stringify() 메서드는 JavaScript 값을 JSON 문자열로 변환합니다.
JSON.stringify
는 JavaScript 값을 JSON으로 변환합니다. 끈. 🎜🎜JSON.parse() 메서드는 JSON 문자열을 구문 분석하여 문자열이 설명하는 JavaScript 값 또는 개체를 구성합니다.🎜🎜
JSON.parse
는 JSON 문자열을 JavaScript 값 또는 개체로 변환합니다. 🎜🎜이해하기 쉽습니다. JavaScript 값과 JSON 문자열 간의 변환입니다. 🎜🎜딥 카피를 달성할 수 있나요? 한번 해보자. 🎜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'}};
cloneObj
에서 일부 속성이 누락된 것을 발견했습니다. . . 왜? 🎜🎜 MDN에서 이유를 찾았습니다: 🎜🎜정의되지 않은 함수 또는 기호가 변환 중에 발견되면 생략되거나(객체에서 발견되는 경우) null로 검열됩니다(배열에서 발견되는 경우). JSON.stringify(함수(){}) 또는 JSON.stringify(정의되지 않음)와 같은 "순수" 값을 전달할 때 JSON.stringify는 정의되지 않은 값을 반환할 수도 있습니다.🎜🎜
정의되지 않음
, 함수
, 기호
는 변환 프로세스 중에 무시됩니다. . . 🎜🎜이해하세요. 즉, 개체에 함수(매우 일반적인)가 포함되어 있으면 이 방법을 사용하여 전체 복사를 수행할 수 없습니다. 🎜🎜🎜재귀적 방법🎜🎜🎜재귀의 개념은 매우 간단합니다. 데이터의 각 레이어에 대해 개체 할당 -> 코드는 간단하고 조잡합니다. 🎜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: ƒ}
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}]
同时,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는 전체 복사를 구현하지만 대상 개체에 대한 요구 사항이 있습니다.
진정한 전체 복사를 원한다면 재귀하세요.
이 기사의 사례를 읽으신 후 방법을 마스터하셨다고 생각합니다. 더 흥미로운 정보를 보려면 PHP 중국어 웹사이트의 다른 관련 기사를 주목하세요!
추천 도서:
process 및 child_process 사용에 대한 자세한 설명
위 내용은 JS의 깊은 복사와 얕은 복사 사용에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!