web-dev-qa-db-fra.com

Manière native de fusionner des objets en Javascript

L'objet Javascript n'a pas d'opération de fusion native. Si vous avez deux objets, dites

{a:1, b:2}
{c:3, d:4}

Et je veux avoir

{a:1, b:2, c:3, d:4}

Pour autant que je sache, vous devez parcourir les objets. C'est-à-dire que vous décidez d'une stratégie de fusion gauche ou droite et ensuite vous faites quelque chose comme (simplifié)

for (key in object2) {
  object1[key] = object2[key];
}

C'est bon. Cependant, Javascript a les fonctionnalités call et prototype. Par exemple, transformer arguments en Array peut être fait avec

Array.prototype.slice.call(arguments)

Cette approche exploite le code natif existant et est donc moins sensible à la folie du programmeur et devrait s'exécuter plus rapidement qu'une implémentation non native.

La question

Y a-t-il une astuce pour utiliser ce prototype/modèle d'appel sur peut-être les fonctions de traversée Attribute ou Node du DOM, ou peut-être certaines des fonctions génériques String afin de faire une fusion d'objets natifs?

Le code ressemblerait à ceci:

var merged = somethingrandom.obscuremethod.call(object1, object2)

Et par conséquent, vous obtiendriez une fusion native sans traversée.

Une solution possible et sous-optimale

Si vous pouviez utiliser la propriété constructor d'un Object, puis contraindre un objet à avoir un constructeur d'un autre objet, puis exécuter new sur l'objet composite, vous pourriez obtenir un fusionner gratuitement. Mais je n'ai pas une bonne compréhension des implications complètes de la fonction constructor en javascript pour effectuer cet appel.

Lemme

La même question vaut pour Arrays. Un problème courant consiste à prendre, disons, 7 tableaux de nombres, puis à essayer de trouver l'intersection de ces tableaux. C'est-à-dire, quels nombres existent dans les 7 tableaux.

Vous pouvez les concaténer ensemble, puis faire un tri, puis faire une traversée, sûrement. Mais ce serait bien s'il y avait une intersection générique cachée quelque part que nous pouvons contraindre un tableau à faire de manière native.

Des pensées?

éditer:

Arriver à mi-chemin

Pour le problème de tableau, vous pouvez procéder comme suit:

array.concat (a, b, c) .sort (). join (':') puis utilisez des schémas de capture et de répétition RegExp délicats pour traverser. Les implémentations RegExp, si vous ne le savez pas, s'exécutent sur une machine virtuelle basée sur une pile très simple. Lorsque vous initialisez votre expression régulière, c'est vraiment un programme qui est compilé (RegExp.compile est une méthode JS obsolète). Ensuite, le natif parcourt la chaîne d'une manière incroyablement rapide. Peut-être pourriez-vous exploiter cela pour les seuils d'adhésion et obtenir de meilleures performances ...

Mais cela ne va pas jusqu'au bout.

47
kristopolous

Ma réponse sera décevante, mais quand même:

non

La raison en est simple: l'implémentation de fusion (ou "d'extension" comme on l'appelle pour les objets) de M. Resig dans jQuery fait une boucle, tout comme celle de votre question. Vous pouvez le regarder ici . Et j'ose dire que si John Resig n'a pas trouvé un moyen intelligent de le faire, les simples mortels de stackoverflow ne le seront pas non plus :)

24
Jakob

La question à un million de dollars! J'ai essayé de le faire de nombreuses façons, et la boucle décrite ci-dessus a toujours semblé la plus sale. Object.setPrototypeOf() d'ES6 vous permet de déléguer un objet "property override" à un objet "default properties", accomplissant à peu près ce que vous essayez de faire, mais l'utilisation de Object.setPrototypeOf() a de sérieuses implications , comme désactiver les optimisations du compilateur du navigateur pour l'ensemble du script.

De plus, dans la solution de boucle et la solution Object.setPrototypeOf(), vous vous retrouvez dans une situation où l'objet "property override" peut muter l'objet "default properties":

defaultObj = {
    a: [1, 2]
}
...
overrideObj = {
    b: 3
}
Object.setPrototypeOf(overrideObj, defaultObj);
console.log(overrideObj); // {a: [1, 2], b: 3}
// Great!
...
overrideObj.a.Push(4);
console.log(defaultObj); // {a: [1, 2, 4]}
// Uh-oh.

Vous pourriez penser que ce n'est pas un problème, mais disons que vous utilisez cet objet comme configuration pour une bibliothèque tierce. Vous donnez maintenant le contrôle de votre objet par défaut et tout ce qui y est référencé à la bibliothèque tierce.

Une meilleure solution pourrait être d'utiliser JSON.stringify et JSON.parse pour copier et combiner les objets. Voici un Gist avec l'exemple: https://Gist.github.com/spikesagal/6f7822466887f19b9c65

HTH

3
Spike Sagal

Pas que je sache, non. De plus, vous voudrez écrire votre méthode de fusion comme ceci:

function mergeInto(o1, o2) {
  if (o1 == null || o2 == null)
    return o1;

  for (var key in o2)
    if (o2.hasOwnProperty(key))
      o1[key] = o2[key];

  return o1;
}
2
Brian Donovan

En utilisant ES6 (ES2015), vous pouvez utiliser la méthode Object.assign:

var x = {a:1, b:2};
var y = {c:3, d:4};
var z = Object.assign({},x,y);

En utilisant ES7 (ES2016, Chrome 60+ ou Babel), vous pouvez utiliser Opérateur de propagation d'objet:

var x = {a:1, b:2};
var y = {c:3, d:4}; 
var z = {...x, ...y};
2
DanSavoy

Vous pouvez effectuer les opérations suivantes à l'aide de JS 1.7 natif, sans avoir besoin d'un framework. Voir l'exemple sur violon (exemple destiné uniquement aux objets simples - pas aux objets imbriqués complexes)

var obj1 = {a: "a", b: "b"};
var obj2 = {c: "c", d: "d"};

// The magic: ugly but works perfectly
var value = (JSON.stringify(obj1).concat(JSON.stringify(obj2))).replace("}{", ",");

document.getElementById("lbl1").setAttribute("value", value);

// back to object
var obj3 = JSON.parse(value);
document.getElementById("lbl2").setAttribute("value", obj3.a + " " + obj3.b + " " + obj3.c + " " + obj3.d);
1
Ron Anon

Pas de méthodes natives dans ECMA-Script, utilisez:

function merge(o1,o2) {
 if (typeof(o1)!=='object') o1={};
 if (typeof(o2)!=='object') o2={};
 for (var k in o2) {
 if (o1[k]!==undefined)
  alert ('Collision Error'); // TODO
 else
   o1[k]=o2[k];
 }
 return o1;
}
0
Informate.it