Maison  >  Article  >  interface Web  >  Bases de JavaScript Explication détaillée des problèmes entre la copie superficielle et la copie profonde

Bases de JavaScript Explication détaillée des problèmes entre la copie superficielle et la copie profonde

亚连
亚连original
2018-06-01 11:21:001070parcourir

La copie superficielle et la copie profonde sont toutes deux destinées aux types de référence dans JS. La copie superficielle copie simplement la référence de l'objet. Si l'objet copié change, l'objet d'origine changera également. Seule la copie profonde est la copie réelle de l'objet

Préface

Quand il s'agit de copie profonde et superficielle, la première chose à mentionner est le type de données JavaScript, l'article précédent L'article "Basic JavaScript Mindset - Data Type" l'explique très clairement, je n'entrerai donc pas dans les détails ici.

Ce que vous devez savoir est une chose : les types de données JavaScript sont divisés en types de données de base et en types de données de référence.

Pour la copie des types de données de base, il n'y a aucune différence entre les copies sombres et superficielles. Ce que nous appelons les copies sombres et superficielles sont destinées aux types de données de référence.

Copie superficielle

Copie superficielle signifie que seule la référence est copiée, mais la valeur réelle n'est pas copiée.

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'}}

Le code ci-dessus est le moyen le plus simple d'utiliser l'opérateur d'affectation = pour implémenter une copie superficielle. Vous pouvez clairement le voir, avec le et cloneArray changent, et cloneObj et originArray changent également. originObj

Copie profonde

La copie profonde est une copie complète de la cible, contrairement à la copie superficielle, qui copie uniquement une couche de références, même la valeur est copiée.

Tant qu'une copie complète est effectuée, ils n'interagiront jamais les uns avec les autres et personne n'affectera l'autre.

Il n'existe pas beaucoup de façons d'implémenter la copie profonde à l'heure actuelle, il existe principalement deux méthodes :

  1. Utiliser analyser et stringifier dans un objet JSON

  2. Utilisez la récursivité pour recréer des objets et attribuer des valeurs à chaque couche

Méthode JSON.stringify/parse

Regardez ce premier Deux méthodes :

La méthode JSON.stringify() convertit une valeur JavaScript en chaîne JSON.

consiste à convertir une valeur JavaScript en chaîne JSON. JSON.stringify

La méthode JSON.parse() analyse une chaîne JSON, en construisant la valeur ou l'objet JavaScript décrit par la chaîne.

consiste à convertir une chaîne JSON en une valeur ou un objet JavaScript . JSON.parse

C'est facile à comprendre, c'est la conversion de valeurs JavaScript et de chaînes JSON.

Peut-il réaliser une copie approfondie ? Essayons.

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'}};

C'est en effet une copie profonde et très pratique. Toutefois, cette méthode ne peut être appliquée qu’à quelques situations simples. Par exemple, l'objet suivant n'est pas applicable :

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"}

On constate que certains attributs manquent dans

. . . Pourquoi? cloneObj

Trouvé la raison sur MDN :

Si une fonction ou un symbole non défini est rencontré lors de la conversion, il est soit omis (lorsqu'il est trouvé dans un objet), soit censuré à null (lorsque il se trouve dans un tableau). JSON.stringify peut également simplement renvoyer undefined lors du passage de valeurs "pures" comme JSON.stringify(function(){}) ou JSON.stringify(undefined).

, undefined, function seront ignorés pendant le processus de conversion. . . symbol

Comprenez-le, c'est-à-dire que si l'objet contient une fonction (très courante), vous ne pouvez pas utiliser cette méthode pour effectuer une copie approfondie.

Méthode récursive

L'idée de la récursion est très simple, c'est-à-dire créer un objet -> opération d'affectation d'objet pour chaque couche de données, simple Rough code :

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;
}

Essayons :

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'}};

Oui. Alors essayez celui avec des fonctions :

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: ƒ}

peut également être utilisé. Fait.

Pensez-vous que c'est la fin ? ? Bien sûr que non.

Méthodes de copie en JavaScript

Nous savons qu'en JavaScript, les tableaux ont deux méthodes, concat et slice, qui peuvent copier le tableau d'origine. Aucune des méthodes. modifiera le tableau d'origine, mais renverra un nouveau tableau modifié.

En même temps, ES6 introduit la méthode

et l'opérateur... spread pour copier également des objets. Object.assgn

S'agit-il de copies superficielles ou de copies profondes ?

concat

La méthode concat() est utilisée pour fusionner deux ou plusieurs tableaux. Cette méthode ne modifie pas les tableaux existants, mais renvoie à la place un nouveau. array.

Cette méthode peut connecter deux tableaux ou plus, mais elle ne modifie pas le tableau existant, mais renvoie un nouveau tableau.

On dirait que cela signifie une copie complète. Essayons :

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];

Cela ressemble à une copie complète.

Considérons un problème, que se passera-t-il si cet objet est multicouche.

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}]

contient un tableau originArray et un objet [1,2,3] Si nous modifions directement le tableau et l'objet, cela n'affectera pas , mais lorsque l'on modifie le tableau {a:1} ou l'objet originArray, on constate que [1,2,3] a également changé. {a:1}

结论: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页面返回不刷新的问题

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn