Domanda Qual è il modo più efficace per clonare in profondità un oggetto in JavaScript?


Qual è il modo più efficace per clonare un oggetto JavaScript? ho visto obj = eval(uneval(o)); in uso, ma questo è non standard e supportato solo da Firefox .

 Ho fatto cose del genere obj = JSON.parse(JSON.stringify(o)); ma dubita dell'efficienza.

 Ho anche visto funzioni di copia ricorsiva con vari difetti.
Sono sorpreso che non esista alcuna soluzione canonica.


4537
2018-06-06 14:59


origine


risposte:


Nota:  Questa è una risposta ad un'altra risposta, non una risposta adeguata a questa domanda. Se desideri avere una clonazione veloce dell'oggetto, segui Il consiglio di Corban nella loro risposta  a questa domanda.


Voglio notare che il .clone() metodo in jQuery  solo cloni elementi DOM. Per clonare oggetti JavaScript, dovresti fare:

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

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

Maggiori informazioni possono essere trovate nel documentazione jQuery .

Voglio anche notare che la copia profonda è in realtà molto più intelligente di quanto mostrato sopra: è in grado di evitare molte trappole (cercando di estendere in profondità un elemento DOM, ad esempio). È usato frequentemente nel core jQuery e nei plugin con grande effetto.


4061



Acquista questo benchmark: http://jsben.ch/#/bWfk9

Nei miei precedenti test in cui la velocità era una preoccupazione principale che ho trovato

JSON.parse(JSON.stringify(obj))

per essere il modo più veloce per clonare in profondità un oggetto (che batte jQuery.extend  con bandiera profonda impostata su true del 10-20%).

jQuery.extend è piuttosto veloce quando il flag deep è impostato su false (clone poco profondo). È una buona opzione, perché include alcune logiche extra per la convalida del tipo e non copia su proprietà non definite, ecc., Ma anche questo rallenterà un po '.

Se si conosce la struttura degli oggetti che si sta tentando di clonare o si possono evitare matrici a nidificazione profonda, è possibile scrivere una semplice for (var i in obj) loop per clonare il tuo oggetto mentre controlli hasOwnProperty e sarà molto più veloce di jQuery.

Infine, se si sta tentando di clonare una struttura di oggetti noti in un ciclo caldo, è possibile ottenere MOLTO MOLTO ALTRO PERFORMANCE semplicemente inserendo la procedura clone e costruendo manualmente l'oggetto.

I motori di tracciamento JavaScript aspirano all'ottimizzazione for..in loop e controllando hasOwnProperty rallenterà anche voi. Clone manuale quando la velocità è assoluta.

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

Stai attento a usare il JSON.parse(JSON.stringify(obj)) metodo attivo Date oggetti - JSON.stringify(new Date()) restituisce una rappresentazione in formato stringa della data in formato ISO, che JSON.parse()  non lo fa  riconvertire in a Date oggetto. Vedi questa risposta per maggiori dettagli .

Inoltre, tieni presente che, almeno in Chrome 65, la clonazione nativa non è la strada da percorrere. Secondo questo JSPerf , eseguire la clonazione nativa creando una nuova funzione è quasi 800x  più lento dell'utilizzo di JSON.stringify, che è incredibilmente veloce su tutta la linea.


1868



Supponendo che tu abbia solo variabili e non funzioni nel tuo oggetto, puoi semplicemente usare:

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

402



Clonazione strutturata

HTML5 definisce un algoritmo di clonazione "strutturato" interno  che può creare profondi cloni di oggetti. È ancora limitato a determinati tipi built-in, ma oltre ai pochi tipi supportati da JSON supporta anche date, RegExps, Maps, Sets, Blob, FileLists, ImageDatas, array sparsi, Array tipizzati e probabilmente più in futuro. Conserva anche i riferimenti all'interno dei dati clonati, permettendogli di supportare strutture cicliche e ricorsive che causerebbero errori per JSON.

Supporto diretto nei browser: in arrivo?

I browser attualmente non forniscono un'interfaccia diretta per l'algoritmo di clonazione strutturata, ma globale structuredClone() la funzione è attivamente discussa in whatwg / html # 793 su GitHub  e potrebbe arrivare presto! Come attualmente proposto, utilizzarlo per la maggior parte degli scopi sarà semplice come:

const clone = structuredClone(original);

Fino a quando questo viene spedito, le implementazioni clone strutturate dei browser sono esposte solo indirettamente.

Soluzione asincrona: utilizzabile.

Il modo più basso di creare un clone strutturato con le API esistenti è di pubblicare i dati attraverso una porta di a MessageChannels . L'altra porta emetterà un message evento con un clone strutturato dell'allegato .data. Sfortunatamente, ascoltare questi eventi è necessariamente asincrono e le alternative sincrone sono meno pratiche.

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

Esempio di utilizzo:

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();

Soluzioni alternative sincrone: terribile!

Non ci sono buone opzioni per creare cloni strutturati in modo sincrono. Ecco invece un paio di hack poco pratici.

history.pushState() e history.replaceState() entrambi creano un clone strutturato del loro primo argomento e assegnano tale valore a history.state. Puoi usarlo per creare un clone strutturato di qualsiasi oggetto come questo:

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

Esempio di utilizzo:

'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();

Sebbene sincrono, può essere estremamente lento. Esso incorre in tutto il sovraccarico associato alla manipolazione della cronologia del browser. La chiamata ripetuta di questo metodo può causare la mancata risposta temporanea di Chrome.

Il Notification costruttore  crea un clone strutturato dei suoi dati associati. Tenta anche di visualizzare una notifica del browser all'utente, ma questo fallirà silenziosamente a meno che tu non abbia richiesto il permesso di notifica. Nel caso in cui tu abbia il permesso per altri scopi, chiuderemo immediatamente la notifica che abbiamo creato.

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

Esempio di utilizzo:

'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



Se non ce n'era uno integrato, potresti provare:

    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



Il modo efficace per clonare (non clonare in profondità) un oggetto in una riga di codice

Un Object.assign il metodo fa parte dello standard ECMAScript 2015 (ES6) e fa esattamente ciò di cui hai bisogno.

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

Il metodo Object.assign () viene utilizzato per copiare i valori di tutte le proprietà enumerabili di uno o più oggetti di origine in un oggetto di destinazione.

Leggi di più...

Il polyfill  per supportare i browser più vecchi:

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



Codice:

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

Test:

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



Questo è quello che sto usando:

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



Copia profonda per rendimento: Classificato dal migliore al peggiore

  • Reassignment "=" (array di stringhe, array di numeri - solo)
  • Slice (array di stringhe, array di numeri - solo)
  • Concatenazione (array di stringhe, array di numeri - solo)
  • Funzione personalizzata: copia per ciclo o ricorsiva
  • $ $ estensione di jQuery
  • JSON.parse (array di stringhe, array di numeri, matrici di oggetti - solo)
  • Underscore.js 's _.clone (array di stringhe, array di numeri - solo)
  • _.cloneDeep di Lo-Dash

Copia in profondità una serie di stringhe o numeri (un livello - nessun riferimento puntatore):

Quando un array contiene numeri e stringhe - funzioni come .slice (), .concat (), .splice (), l'operatore di assegnazione "=" e la funzione clone di Underscore.js; farà una copia profonda degli elementi dell'array.

Dove la riassegnazione ha le prestazioni più veloci:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

E .slice () ha prestazioni migliori di .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Copia in profondità una serie di oggetti (due o più livelli - puntatori di riferimento):

var arr1 = [{object:'a'}, {object:'b'}];

Scrivi una funzione personalizzata (ha prestazioni più veloci di $ .extend () o JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Utilizzare le funzioni di utilità di terze parti:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Dove $ .extend di jQuery ha prestazioni migliori:


64