Question Quel est le moyen le plus efficace pour cloner en profondeur un objet en JavaScript?


Quel est le moyen le plus efficace de cloner un objet JavaScript? j'ai vu obj = eval(uneval(o)); être utilisé, mais c'est non standard et seulement supporté par Firefox.

 J'ai fait des choses comme obj = JSON.parse(JSON.stringify(o)); mais questionne l'efficacité.

 J'ai également vu des fonctions de copie récursives avec divers défauts.
Je suis surpris qu'aucune solution canonique n'existe.


4538
2018-06-06 14:59


origine


Réponses:


Remarque: Ceci est une réponse à une autre réponse, pas une réponse appropriée à cette question. Si vous souhaitez avoir un clonage rapide des objets, veuillez suivre Le conseil de Corban dans sa réponse à cette question.


Je veux noter que le .clone() méthode dans jQuery ne clone que des éléments DOM. Pour cloner des objets JavaScript, vous devez:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Plus d'informations peuvent être trouvées dans le Documentation jQuery.

Je veux également noter que la copie profonde est en réalité beaucoup plus intelligente que ce qui est montré ci-dessus - elle est capable d'éviter de nombreux pièges (en essayant d'étendre en profondeur un élément DOM, par exemple). Il est fréquemment utilisé dans le noyau jQuery et dans les plugins pour un effet optimal.


4062



Commander ce benchmark: http://jsben.ch/#/bWfk9

Dans mes tests précédents où la vitesse était une préoccupation majeure, j'ai trouvé

JSON.parse(JSON.stringify(obj))

être le moyen le plus rapide de cloner en profondeur un objet (il bat jQuery.extend avec le drapeau profond réglé de 10 à 20%).

jQuery.extend est assez rapide lorsque le drapeau profond est défini sur false (clone superficiel). C'est une bonne option, car elle inclut une logique supplémentaire pour la validation de type et ne copie pas les propriétés non définies, etc., mais cela vous ralentira également un peu.

Si vous connaissez la structure des objets que vous essayez de cloner ou pouvez éviter les tableaux imbriqués profonds, vous pouvez écrire un simple for (var i in obj) boucle pour cloner votre objet tout en vérifiant hasOwnProperty et il sera beaucoup plus rapide que jQuery.

Enfin, si vous tentez de cloner une structure d'objet connue dans une boucle chaude, vous pouvez OBTENIR BEAUCOUP PLUS DE PERFORMANCES en insérant simplement la procédure de clonage et en construisant manuellement l'objet.

Les moteurs de trace JavaScript aspirent à l'optimisation for..in boucles et vérifier hasOwnProperty vous ralentira aussi bien. Clone manuel lorsque la vitesse est un must absolu.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Méfiez-vous en utilisant le JSON.parse(JSON.stringify(obj)) méthode sur Date objets - JSON.stringify(new Date()) renvoie une représentation sous forme de chaîne de la date au format ISO, JSON.parse()  ne pas convertir en un Date objet. Voir cette réponse pour plus de détails.

De plus, notez que, dans Chrome 65 au moins, le clonage natif n'est pas la solution. Selon ce JSPerf, effectuer le clonage natif en créant une nouvelle fonction est presque 800x plus lent que d'utiliser JSON.stringify qui est incroyablement rapide tout au long du tableau.


1868



En supposant que vous avez seulement des variables et pas de fonctions dans votre objet, vous pouvez simplement utiliser:

var newObject = JSON.parse(JSON.stringify(oldObject));

402



Clonage structuré

HTML5 définit un algorithme de clonage interne "structuré" Cela peut créer des clones profonds d'objets. Il est toujours limité à certains types prédéfinis, mais en plus des quelques types pris en charge par JSON, il prend également en charge les dates, les RegExps, les Maps, les Sets, les Blobs, les FileLists, les ImageDatas, les Sparse Arrays, Tableaux typés, et probablement plus dans le futur. Il préserve également les références dans les données clonées, ce qui lui permet de prendre en charge des structures cycliques et récursives qui provoqueraient des erreurs pour JSON.

Support direct dans les navigateurs: bientôt disponible?

Les navigateurs ne fournissent pas actuellement une interface directe pour l'algorithme de clonage structuré, mais un structuredClone() la fonction est activement discutée dans whatwg / html # 793 sur GitHub et peut être bientôt! Comme proposé actuellement, l'utiliser pour la plupart des objectifs sera aussi simple que:

const clone = structuredClone(original);

Jusqu'à leur livraison, les implémentations de clones structurés des navigateurs ne sont exposées qu'indirectement.

Solution de contournement asynchrone: utilisable.

Le moyen le plus simple de créer un clone structuré avec des API existantes consiste à publier les données via un port d'un MessageChannels. L'autre port émettra un message événement avec un clone structuré de la pièce jointe .data. Malheureusement, l'écoute de ces événements est nécessairement asynchrone, et les alternatives synchrones sont moins pratiques.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Exemple d'utilisation:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Solutions de contournement synchrones: affreux!

Il n'y a pas de bonnes options pour créer des clones structurés de manière synchrone. Voici quelques hacks impraticables à la place.

history.pushState() et history.replaceState() les deux créent un clone structuré de leur premier argument, et assignent cette valeur à history.state. Vous pouvez l'utiliser pour créer un clone structuré d'un objet comme celui-ci:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Exemple d'utilisation:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

Bien que synchrone, cela peut être extrêmement lent. Il engage tous les frais généraux associés à la manipulation de l'historique du navigateur. L'appel répété de cette méthode peut entraîner temporairement l'arrêt de Chrome.

le Notification constructeur crée un clone structuré de ses données associées. Il tente également d'afficher une notification du navigateur à l'utilisateur, mais cela échouera silencieusement, sauf si vous avez demandé l'autorisation de notification. Au cas où vous en auriez l'autorisation à d'autres fins, nous fermerons immédiatement la notification que nous avons créée.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Exemple d'utilisation:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();


274



S'il n'y en avait pas, vous pourriez essayer:

    function clone(obj) {
      if (obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

      if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
      else
        var temp = obj.constructor();

      for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          obj['isActiveClone'] = null;
          temp[key] = clone(obj[key]);
          delete obj['isActiveClone'];
        }
      }

      return temp;
    }


273



La manière efficace de cloner (pas de cloner en profondeur) un objet dans une ligne de code

Un Object.assign Cette méthode fait partie de la norme ECMAScript 2015 (ES6) et fait exactement ce dont vous avez besoin.

var clone = Object.assign({}, obj);

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.

Lire la suite...

le polyfill pour supporter les anciens navigateurs:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

132



Code:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Tester:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

91



C'est ce que j'utilise:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

81