머리말
깊고 얕은 복사에 관해서는 먼저 JavaScript의 데이터 유형을 언급해야 합니다. 이전 기사 "기본 JavaScript Mental Methods - 데이터 유형"에서 매우 명확하게 설명했기 때문에 자세히 설명하지 않겠습니다. 여기.
당신이 알아야 할 것은 한 가지입니다: 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도 변경된다는 것을 분명히 알 수 있습니다.
Deep copy
Deep copy는 참조 레이어만 복사하는 얕은 복사와 달리 대상의 전체 복사본입니다.
딥 카피가 만들어지는 한, 그들은 서로 상호 작용하지 않으며 누구도 서로에게 영향을 미치지 않습니다.
현재 딥 카피를 구현하는 방법은 많지 않으며 주로 두 가지 방법이 있습니다.
JSON 객체에서 구문 분석 및 문자열화 사용
재귀를 사용하여 객체를 다시 생성하고 각 레이어에 값 할당
JSON.stringify/ 구문 분석 메서드
먼저 이 두 가지 메서드를 살펴보겠습니다.
JSON.stringify() 메서드는 JavaScript 값을 JSON 문자열로 변환합니다.
JSON.stringify는 JavaScript 값을 JSON 문자열로 변환합니다.
JSON.parse() 메서드는 JSON 문자열을 구문 분석하여 문자열이 설명하는 JavaScript 값 또는 개체를 구성합니다.
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에서 이유를 찾았습니다.
정의되지 않은 함수 또는 기호가 변환 중에 발견되면 생략되거나(객체에서 발견되는 경우) null로 검열됩니다(배열에서 발견되는 경우). JSON.stringify는 JSON.stringify(function(){}) 또는 JSON.stringify(undefine)과 같은 "순수" 값을 전달할 때 정의되지 않은 값을 반환할 수도 있습니다.
undefine, 함수, 기호는 변환 프로세스 중에 무시됩니다. . . .
이해하세요. 즉, 개체에 함수(매우 일반적인)가 포함되어 있으면 이 방법을 사용하여 전체 복사를 수행할 수 없습니다.
재귀적 방법
재귀의 개념은 매우 간단합니다. 각 데이터 계층에 대해 개체 -> 개체 할당 작업을 만드는 것입니다. 간단하고 조잡한 코드:
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과 슬라이스라는 두 가지 메서드가 있다는 것을 알고 있습니다. 이 두 메서드 중 어느 것도 원래 배열을 수정하지 않고 수정된 새 배열을 반환합니다.
동시에 ES6에서는 Object.assgn 메서드와... 확장 연산자를 도입하여 객체도 복사합니다.
얕은 복사본인가요, 깊은 복사본인가요?
concat
concat() 메서드는 두 개 이상의 배열을 병합하는 데 사용됩니다. 이 메서드는 기존 배열을 변경하지 않고 대신 새 배열을 반환합니다.
이 메서드는 두 개 이상의 배열을 연결할 수 있지만 그렇지 않습니다. 기존 배열을 수정하지만 새 배열을 반환합니다.
뜻을 보니 딥카피 같네요. 한번 해보세요:
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
slice() 메서드는 배열 일부의 얕은 복사본을 처음부터 끝까지 선택된 새 배열 객체로 반환합니다(끝은 포함되지 않음). 원본 배열은 수정되지 않습니다.
설명은 간단합니다. 얕은 카피로 썼어요~
하지만 그렇지 않아요!
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과 동일합니다.
결론: 슬라이스는 배열의 첫 번째 수준에 대한 전체 복사본을 만듭니다.
Object.sign()
Object.sign() 메소드는 하나 이상의 소스 객체에서 열거 가능한 모든 자체 속성 값을 대상 객체로 복사하는 데 사용됩니다.
Copy 복사 복사 .
그럼 얕은 카피인가요, 딥 카피인가요?
직접 시도해 보세요. .
结论: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 实现的是深拷贝,但是对目标对象有要求;
若想真正意义上的深拷贝,请递归。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
用p5.js制作烟花特效的示例代码_javascript技巧
위 내용은 기본 JavaScript 팁(그림 및 텍스트 튜토리얼, 자세한 답변)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!