Avant-propos
En ce qui concerne la copie profonde et superficielle, nous devons d'abord mentionner les types de données de JavaScript L'article précédent Bases de JavaScript - Types de données l'a dit très clairement, pas. beaucoup à dire 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
La 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. On peut clairement voir qu'à mesure que cloneArray et cloneObj changent, originArray et originObj changent également.
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'y a pas beaucoup de façons d'implémenter la copie profonde à l'heure actuelle, il existe principalement deux méthodes :
Utilisez parse et stringify dans l'objet JSON
Utilisez la récursivité pour recréer chaque couche Objet et attribuer une valeur
Méthode JSON.stringify/parse
Jetons d'abord un coup d'œil à ces deux méthodes :
La méthode JSON.stringify() convertit une valeur JavaScript en une chaîne JSON.
JSON.stringify convertit une valeur JavaScript en chaîne JSON.
La méthode JSON.parse() analyse une chaîne JSON, en construisant la valeur ou l'objet JavaScript décrit par la chaîne.
JSON.parse consiste à convertir une chaîne JSON en une valeur ou un objet JavaScript .
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"}
Il s'avère que certains attributs manquent dans cloneObj. . . Pourquoi?
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 , fonction, symbole seront ignorés pendant le processus de conversion. . .
Comprenez, 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. Il s'agit de créer un objet une fois pour chaque couche de données -> opération d'affectation d'objet :
.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'}}; 可以。那再试试带有函数的: 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: ƒ}
Aussi. 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 de ces deux méthodes ne modifiera le tableau d'origine. , renvoyant à la place un nouveau tableau modifié.
Dans le même temps, ES6 a introduit la méthode Object.assgn et l'opérateur... spread pour copier également des objets.
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 tableau.
Le La méthode peut concaténer deux ou plusieurs tableaux, 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}]
originArray contient le tableau [1,2,3] et l'objet {a:1} Si nous modifions directement le tableau et l'objet, cela n'affectera pas originArray, mais nous modifions le tableau [1, 2, 3] ou l'objet {a:1}, on constate que originArray a également changé.
Conclusion : concat ne fait qu'une copie complète du premier niveau du tableau.
slice
La méthode slice() renvoie une copie superficielle d'une partie d'un tableau dans un nouvel objet tableau sélectionné du début à la fin (la fin n'est pas incluse). modifié.
L'explication indique directement qu'il s'agit d'une copie superficielle~
Mais ce n'est pas le cas !
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];
De même, essayons un tableau à plusieurs niveaux.
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}]
Effectivement, le résultat est le même que concat.
Conclusion : slice ne fait qu'une copie complète du premier niveau du tableau.
Object.assign()
La méthode Object.assign() est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers un objet cible. renvoie l'objet cible.
Copier copie copie.
Est-ce une copie superficielle ou une copie profonde ?
Essayez-le vous-même. .
结论: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技巧
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!