Maison  >  Article  >  Quelles sont les trois méthodes de mise en œuvre de la copie profonde ?

Quelles sont les trois méthodes de mise en œuvre de la copie profonde ?

coldplay.xixi
coldplay.xixioriginal
2020-12-01 12:01:4535304parcourir

Les trois méthodes d'implémentation de la copie profonde sont : 1. Copier récursivement tous les attributs hiérarchiques ; 2. Implémenter en utilisant l'analyse et la stringification des objets JSON ; 3. Emprunter la méthode d'extension de JQ ;

Quelles sont les trois méthodes de mise en œuvre de la copie profonde ?

Les trois méthodes d'implémentation de la copie profonde sont :

1, copier récursivement tous les attributs hiérarchiques

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};    if(obj && typeof obj==="object"){        for(key in obj){            if(obj.hasOwnProperty(key)){                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);

Comme imaginé auparavant, b est désormais hors du contrôle de a et n'est plus affecté par a.

J'insiste encore une fois sur le fait que la copie profonde consiste à copier les attributs de chaque niveau de l'objet. Vous pouvez voir un exemple. Il existe une méthode d'extension dans JQ qui peut également copier des objets. Voyons si

let a=[1,2,3,4],
    b=a.slice();
a[0]=2;
console.log(a,b);

cela signifie-t-il que la méthode slice est également une copie complète après tout, b ? non affecté par a. , comme mentionné ci-dessus, la copie profonde copiera-t-elle les attributs de tous les niveaux ? Dans cet exemple, nous changeons a en

La copie n'est pas terminée. .Un des objets b Les attributs de premier niveau ne sont en effet pas affectés, mais les attributs de deuxième niveau ne parviennent toujours pas à être copiés avec succès et ne peuvent toujours pas échapper au contrôle de a, ce qui indique que slice n'est pas du tout une véritable copie profonde.

Voici une citation de Zhihu Q&A

Les attributs de la première couche sont en effet copiés en profondeur, avec une mémoire indépendante, mais des attributs plus profonds Mais l'adresse est toujours partagé, c'est pourquoi le problème ci-dessus est causé.

De même, la méthode concat et slice ont également cette situation. Ce ne sont pas de vraies copies profondes. Vous devez faire attention ici.

2. En plus de la récursivité, nous pouvons également emprunter l'analyse et la stringification d'objets JSON

let a=[0,1,[2,3],4],
        b=a.slice();
a[0]=1;
a[2][0]=1;
console.log(a,b);

Comme vous pouvez le voir, cette fois b Il n'est absolument pas affecté par a.

À propos, en plus d'implémenter la copie approfondie, JSON.stringify et JSON.parse peuvent également être combinés avec localStorage pour implémenter le stockage de tableaux d'objets. Si vous êtes intéressé, vous pouvez lire cet article du blogueur.

localStorage stocke des tableaux, des objets, localStorage, sessionStorage stocke des objets de tableau

3 En plus des deux méthodes ci-dessus, nous pouvons également emprunter la méthode d'extension de JQ.

$.extend( [deep ], target, object1 [, objectN ] )

  • deep indique s'il faut copier profondément. Si c'est vrai, c'est une copie profonde. Si c'est faux, c'est une copie superficielle.

  • Objet
  • type objet cible, les propriétés membres des autres objets seront attachées à cet objet.

    object1 objectN

  • Facultatif. Type d'objet Le premier et le Nième objet fusionnés.
  • function deepClone(obj){
        let _obj = JSON.stringify(obj),
            objClone = JSON.parse(_obj);
        return objClone
    }    
    let a=[0,1,[2,3],4],
        b=deepClone(a);
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);
    Vous pouvez voir que l'effet est le même que la méthode ci-dessus, mais vous devez vous fier à la bibliothèque JQ.

Cela dit, comprendre la copie approfondie n'est pas seulement pour faire face aux questions d'entretien, c'est également très utile dans le développement réel. Par exemple, un ensemble de données est renvoyé en arrière-plan et vous devez effectuer des opérations sur cet ensemble de données. Cependant, dans le cas d'un développement multi-personnes, vous n'avez aucun moyen de savoir si cet ensemble de données a d'autres fonctions. qui doivent être utilisés. La modification directe peut entraîner des problèmes cachés. La copie peut vous aider à exploiter les données de manière plus sûre et sécurisée en fonction de la situation réelle, ce qui est probablement ce que cela signifie.

4._.cloneDeep() de lodash

Ce qui suit est une solution au problème de copie approfondie que j'ai lu. JSON.parse

Convertissez d'abord un objet en objet json. Analysez ensuite cet objet json.

let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

L'avantage de cette méthode est que le code est relativement simple à écrire. Mais les défauts sont également évidents. Vous créez d’abord une chaîne temporaire, éventuellement volumineuse, uniquement pour la remettre dans l’analyseur. Un autre inconvénient est que cette méthode ne peut pas gérer les objets cycliques. L'objet de boucle suivant lèvera une exception lors de l'utilisation de cette méthode

let obj = {a:{b:22}};let copy = JSON.parse(JSON.stringify(obj));

telle que Map, Set, RegExp, Date, ArrayBuffer et d'autres éléments intégrés Les types sont perdus lors de la sérialisation.

let a = {};let b = {a};a.b = b;let copy = JSON.parse(JSON.stringify(a));
La valeur de a est imprimée comme suit

Quelles sont les trois méthodes de mise en œuvre de la copie profonde ?

copy的值打印如下

Quelles sont les trois méthodes de mise en œuvre de la copie profonde ?

对比发现,Set已丢失。

Structured Clone 结构化克隆算法

MessageChannel

建立两个端,一个端发送消息,另一个端接收消息。

function structuralClone(obj) {    return new Promise(resolve =>{
        const {port1, port2} = new MessageChannel();
        port2.onmessage = ev => resolve(ev.data);
        port1.postMessage(obj);
    })
}
const obj = /* ... */;
structuralClone(obj).then(res=>{
     console.log(res);
})

这种方法的优点就是能解决循环引用的问题,还支持大量的内置数据类型。缺点就是这个方法是异步的。

History API

利用history.replaceState。这个api在做单页面应用的路由时可以做无刷新的改变url。这个对象使用结构化克隆,而且是同步的。但是我们需要注意,在单页面中不要把原有的路由逻辑搞乱了。所以我们在克隆完一个对象的时候,要恢复路由的原状。

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);  return copy;
}var obj = {};var b = {obj};
obj.b = bvar copy = structuralClone(obj); 
console.log(copy);

这个方法的优点是。能解决循环对象的问题,也支持许多内置类型的克隆。并且是同步的。但是缺点就是有的浏览器对调用频率有限制。比如Safari 30 秒内只允许调用 100 次

Notification API

这个api主要是用于桌面通知的。如果你使用Facebook的时候,你肯定会发现时常在浏览器的右下角有一个弹窗,对就是这家伙。我们也可以利用这个api实现js对象的深拷贝。

function structuralClone(obj) {  return new Notification('', {data: obj, silent: true}).data;
}var obj = {};var b = {obj};
obj.b = bvar copy = structuralClone(obj);
console.log(copy)

同样是优点和缺点并存,优点就是可以解决循环对象问题,也支持许多内置类型的克隆,并且是同步的。缺点就是这个需要api的使用需要向用户请求权限,但是用在这里克隆数据的时候,不经用户授权也可以使用。在http协议的情况下会提示你再https的场景下使用。

lodash的_.cloneDeep()

支持循环对象,和大量的内置类型,对很多细节都处理的比较不错。推荐使用。

支持的类型有很多

Quelles sont les trois méthodes de mise en œuvre de la copie profonde ?

我们这里再次关注一下lodash是如何解决循环应用这个问题的?

Quelles sont les trois méthodes de mise en œuvre de la copie profonde ?

从相关的代码中。我们可以发现。lodash是用一个栈记录了。所有被拷贝的引用值。如果再次碰到同样的引用值的时候,不会再去拷贝一遍。而是利用之前已经拷贝好的值。

实现一个简易点的深拷贝,以解决循环引用的问题为目标

我们仅仅实现一个简易点的深拷贝。能优雅的处理循环引用的即可。在实现深拷贝之前,我们首先温习回顾一下js中的遍历对象的属性的方法和各种方法的优缺点。

js中遍历一个对象的属性的方法

  • Object.keys() 仅仅返回自身的可枚举属性,不包括继承来的,更不包括Symbol属性
  • Object.getOwnPropertyNames() 返回自身的可枚举和不可枚举属性。但是不包括Symbol属性
  • Object.getOwnPropertySymbols() 返回自身的Symol属性
  • for...in 可以遍历对象的自身的和继承的可枚举属性,不包含Symbol属性
  • Reflect.ownkeys() 返回对象自身的所有属性,不管是否可枚举,也不管是否是Symbol。注意不包括继承的属性

实现深拷贝,解决循环引用问题

/**
 * 判断是否是基本数据类型
 * @param value 
 */function isPrimitive(value){  return (typeof value === 'string' || 
  typeof value === 'number' || 
  typeof value === 'symbol' ||  typeof value === 'boolean')
}/**
 * 判断是否是一个js对象
 * @param value 
 */function isObject(value){  return Object.prototype.toString.call(value) === "[object Object]"}/**
 * 深拷贝一个值
 * @param value 
 */function cloneDeep(value){  // 记录被拷贝的值,避免循环引用的出现
  let memo = {};  function baseClone(value){
    let res;    // 如果是基本数据类型,则直接返回
    if(isPrimitive(value)){      return value;    // 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值
    }else if(Array.isArray(value)){
      res = [...value];
    }else if(isObject(value)){
      res = {...value};
    }    // 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝
    Reflect.ownKeys(res).forEach(key=>{      if(typeof res[key] === "object" && res[key]!== null){        //此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题
        if(memo[res[key]]){
          res[key] = memo[res[key]];
        }else{
          memo[res[key]] = res[key];
          res[key] = baseClone(res[key])
        }
      }
    })    return res;  
  }  return baseClone(value)
}

验证我们写的cloneDeep是否能解决循环应用的问题

var obj = {};var b = {obj};
obj.b = bvar copy = cloneDeep(obj); 
console.log(copy);

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
Article précédent:Quel logiciel est au ?Article suivant:Quel logiciel est au ?