>  기사  >  웹 프론트엔드  >  JavaScript 기초 얕은 복사와 깊은 복사 사이의 문제에 대한 자세한 설명

JavaScript 기초 얕은 복사와 깊은 복사 사이의 문제에 대한 자세한 설명

亚连
亚连원래의
2018-06-01 11:21:001080검색

Shallow copy와 deep copy는 모두 JS의 참조 유형입니다. Shallow copy는 복사된 객체가 변경되면 원본 객체도 변경됩니다. 깊은 복사만이 객체의 실제 복사본입니다

머리말

깊은 복사와 얕은 복사에 관해 먼저 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'}}

위 코드는 = 대입 연산자를 사용하여 얕은 복사본을 구현하는 가장 간단한 방법입니다. cloneArraycloneObj 변경을 통해 이를 명확하게 확인할 수 있습니다. , originArray OriginObj 도 변경되었습니다. cloneArraycloneObj 改变,originArray originObj 也随着发生了变化。

深拷贝

深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。

只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。

目前实现深拷贝的方法不多,主要是两种:

  1. 利用 JSON 对象中的 parse 和 stringify

  2. 利用递归来实现每一层都重新创建对象并赋值

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).

undefinedfunctionsymbol 会在转换过程中被忽略。。。

明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝。

递归的方法

递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码:

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

🎜딥 카피🎜🎜🎜딥 카피는 대상의 전체 복사본입니다. 참조 레이어만 복사하는 얕은 복사본과 달리 값까지 복사됩니다. 🎜🎜딥 카피가 만들어지는 한, 그들은 서로 상호 작용하지 않으며 누구도 서로에게 영향을 미치지 않습니다. 🎜🎜현재 전체 복사를 구현하는 방법은 많지 않으며 주로 두 가지 방법이 있습니다: 🎜
  1. 🎜JSON 개체에서 구문 분석 및 문자열화 사용🎜
  2. 🎜 재귀를 사용하여 객체를 다시 생성하고 각 레이어에 값을 할당합니다🎜
🎜🎜JSON.stringify/parse 메서드🎜🎜🎜먼저 이 두 가지 메서드를 살펴보겠습니다. 🎜🎜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 = 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}]
🎜🎜🎜 cloneObj 에서 일부 속성이 누락된 것을 발견했습니다. . . 왜? 🎜🎜 MDN에서 이유를 찾았습니다. 🎜🎜정의되지 않은 함수 또는 기호가 변환 중에 발견되면 생략되거나(객체에서 발견되는 경우) null로 검열됩니다(배열에서 발견되는 경우). JSON.stringify는 JSON.stringify(function(){}) 또는 JSON.stringify(undefine)과 같은 "순수" 값을 전달할 때 정의되지 않은 값을 반환할 수도 있습니다.🎜🎜undefine 함수 , 기호 는 변환 과정에서 무시됩니다. . . 🎜🎜이해하세요. 즉, 개체에 함수(매우 일반적인)가 포함되어 있으면 이 방법을 사용하여 전체 복사를 수행할 수 없습니다. 🎜🎜🎜재귀적 방법🎜🎜🎜재귀의 개념은 매우 간단합니다. 데이터의 각 레이어에 대해 개체 할당 작업을 만드는 것입니다. 코드는 간단하고 조잡합니다. 🎜🎜🎜
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}}
🎜🎜🎜We입니다. 와서 시도해 보세요: 🎜🎜🎜
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';
🎜🎜🎜도 사용할 수 있습니다. 완료. 🎜🎜이것이 끝이라고 생각하시나요? ? 물론 그렇지 않습니다. 🎜🎜🎜JavaScript의 복사 메서드🎜🎜🎜우리는 JavaScript에서 배열에 원래 배열을 복사할 수 있는 concat과 슬라이스라는 두 가지 메서드가 있다는 것을 알고 있습니다. 이 두 메서드는 원래 배열을 수정하지 않고 수정된 새 배열을 반환합니다. 🎜🎜동시에 ES6에서는 Object.assgn 메서드와... 스프레드 연산자를 도입하여 객체도 복사했습니다. 🎜🎜얕은 복사본인가요, 깊은 복사본인가요? 🎜🎜🎜concat🎜🎜🎜🎜concat() 메서드는 두 개 이상의 배열을 병합하는 데 사용됩니다. 이 메서드는 기존 배열을 변경하지 않고 대신 새 배열을 반환합니다.🎜🎜🎜이 메서드는 두 개 이상의 배열을 연결할 수 있습니다. 하지만 기존 배열을 수정하지는 않지만 새 배열을 반환합니다. 🎜🎜뜻을 보니 딥카피 같네요. 한번 해보세요: 🎜🎜🎜
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'}}
🎜🎜🎜딥카피 같네요. 🎜🎜이 물체가 여러 겹으로 쌓이면 어떤 일이 일어날지 문제를 생각해 봅시다. 🎜🎜🎜rrreee🎜🎜🎜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 中的一个对象被修改了。。。说好的深拷贝呢?不是引用地址都不一样了吗?

原来是这样:

  1. 从 shallowClone 的代码中我们可以看出,我们只对第一层的目标进行了 深拷贝 ,而第二层开始的目标我们是直接利用 = 赋值操作符进行拷贝的。

  2. so,第二层后的目标都只是复制了一个引用,也就是浅拷贝。

总结

  1. 赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;

  2. JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;

  3. JSON.stringify 实现的是深拷贝,但是对目标对象有要求;

  4. 若想真正意义上的深拷贝,请递归。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

vue 简单自动补全的输入框的示例

解决vue页面刷新或者后退参数丢失的问题

解决vue单页使用keep-alive页面返回不刷新的问题

위 내용은 JavaScript 기초 얕은 복사와 깊은 복사 사이의 문제에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.