Vraag Wat is de meest efficiënte manier om een ​​object diep in een JavaScript te klonen?


Wat is de meest efficiënte manier om een ​​JavaScript-object te klonen? ik heb gezien obj = eval(uneval(o)); wordt gebruikt, maar dat is niet-standaard en wordt alleen ondersteund door Firefox.

 Ik heb dingen gedaan zoals obj = JSON.parse(JSON.stringify(o)); maar vraag de efficiëntie.

 Ik heb ook recursieve kopieerfuncties met verschillende tekortkomingen gezien.
Ik ben verrast dat er geen canonieke oplossing bestaat.


4538
2018-06-06 14:59


oorsprong


antwoorden:


Notitie: Dit is een antwoord op een ander antwoord, geen juist antwoord op deze vraag. Als u snel objecten wilt klonen, volg dan alstublieft Corban's advies in hun antwoord op deze vraag.


Ik wil opmerken dat het .clone() methode in jQuery alleen DOM-elementen klonen. Als u JavaScript-objecten wilt klonen, doet u het volgende:

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

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

Meer informatie is te vinden in de jQuery-documentatie.

Ik wil ook opmerken dat de diepe kopie eigenlijk veel slimmer is dan wat hierboven wordt getoond - het is in staat om veel vallen te vermijden (bijvoorbeeld om een ​​DOM-element diep uit te breiden). Het wordt vaak gebruikt in jQuery-kern en in plug-ins met groot effect.


4062



Bekijk deze benchmark: http://jsben.ch/#/bWfk9

In mijn eerdere tests, waarbij snelheid een hoofdbekommernis was, vond ik dat

JSON.parse(JSON.stringify(obj))

om de snelste manier te zijn om een ​​object diep te klonen (het verslaat) jQuery.extend met een diepe vlag die met 10-20% waar is).

jQuery.extend is vrij snel wanneer de diepe vlag is ingesteld op false (ondiepe kloon). Het is een goede optie, omdat het wat extra logica bevat voor typevalidatie en niet kopieert over niet-gedefinieerde eigenschappen, enz., Maar dit zal ook een beetje vertragen.

Als u de structuur kent van de objecten die u probeert te klonen of diepe geneste arrays kunt vermijden, kunt u een eenvoudige schrijven for (var i in obj) lus om je object te klonen terwijl je hasOwnProperty controleert en het zal veel sneller zijn dan jQuery.

Als u ten slotte probeert een bekende objectstructuur in een warme lus te klonen, kunt u VEEL VEEL MEER PRESTATIES krijgen door de kloonprocedure eenvoudig in te voegen en het object handmatig te construeren.

JavaScript-traceermachines zijn slecht in het optimaliseren for..in loops en checking hasOwnProperty vertragen je ook. Handmatige kloon wanneer snelheid een absolute must is.

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

Pas op voor gebruik van de JSON.parse(JSON.stringify(obj)) methode op Date voorwerpen - JSON.stringify(new Date()) retourneert een tekenreeksrepresentatie van de datum in ISO-indeling, die JSON.parse()  niet converteren naar a Date voorwerp. Zie dit antwoord voor meer details.

Houd er ook rekening mee dat in native native 65 klonen niet de juiste manier is. Volgens deze JSPerf, het uitvoeren van native klonen door het maken van een nieuwe functie is bijna 800x langzamer dan het gebruik van JSON.stringify, dat ongelooflijk snel is over het hele bord.


1868



Ervan uitgaande dat u alleen variabelen en geen functies in uw object hebt, kunt u gewoon gebruik maken van:

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

402



Gestructureerd klonen

HTML5 definieert een intern "gestructureerd" kloneringsalgoritme die diepe klonen van objecten kan maken. Het is nog steeds beperkt tot bepaalde ingebouwde typen, maar in aanvulling op de paar typen die door JSON worden ondersteund, ondersteunt het ook Data, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Getypeerde reeksen, en waarschijnlijk meer in de toekomst. Het bewaart ook verwijzingen binnen de gekloonde gegevens, waardoor het cyclische en recursieve structuren ondersteunt die fouten voor JSON zouden kunnen veroorzaken.

Directe ondersteuning in browsers: binnenkort beschikbaar?

Browsers bieden momenteel geen directe interface voor het gestructureerde algoritme voor klonen, maar een globale structuredClone() functie wordt actief besproken in whatwg / html # 793 op GitHub en kan binnenkort komen! Zoals momenteel wordt voorgesteld, zal het gebruik voor de meeste doeleinden even eenvoudig zijn als:

const clone = structuredClone(original);

Totdat dit is verzonden, worden de gestructureerde kloonimplementaties van browsers alleen indirect weergegeven.

Asynchrone oplossing: bruikbaar.

De lagerliggende manier om een ​​gestructureerde kloon met bestaande API's te maken, is om de gegevens via één poort van een te plaatsen MessageChannels. De andere poort geeft a message evenement met een gestructureerde kloon van de bijgevoegde .data. Helaas is luisteren naar deze evenementen noodzakelijkerwijs asynchroon en zijn de synchrone alternatieven minder praktisch.

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

Voorbeeld gebruik:

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

Synchronous Workarounds: Awful!

Er zijn geen goede opties om synchroon gestructureerde klonen te maken. Hier zijn een paar onpraktische hacks.

history.pushState() en history.replaceState() beide maken een gestructureerde kloon van hun eerste argument en wijzen die waarde toe aan history.state. Je kunt dit gebruiken om een ​​gestructureerde kloon van elk object zoals dit te maken:

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

Voorbeeld gebruik:

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

Hoewel dit synchroon is, kan dit extreem traag zijn. Het loopt alle overhead op die hoort bij het manipuleren van de browsergeschiedenis. Als u deze methode herhaaldelijk gebruikt, kan Chrome tijdelijk niet meer reageren.

De Notification bouwer maakt een gestructureerde kloon van de bijbehorende gegevens. Het probeert ook om een ​​browsermelding voor de gebruiker weer te geven, maar dit zal in stilte mislukken, tenzij u om een ​​machtiging tot notificatie hebt gevraagd. In het geval dat u de toestemming hebt voor andere doeleinden, zullen we de melding die we hebben gemaakt onmiddellijk sluiten.

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

Voorbeeld gebruik:

'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



Als er geen enkele is gebouwd, kunt u het volgende proberen:

    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



De efficiënte manier om een ​​object in een regel code te klonen (niet deep-clone)

Een Object.assign methode maakt deel uit van de ECMAScript 2015 (ES6) -standaard en doet precies wat u nodig hebt.

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

De methode Object.assign () wordt gebruikt om de waarden van alle opsombare eigen eigenschappen van een of meer bronobjecten naar een doelobject te kopiëren.

Lees verder...

De polyfill om oudere browsers te ondersteunen:

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

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



Dit is wat ik gebruik:

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