Pytanie Jak mogę dynamicznie scalać właściwości dwóch obiektów JavaScript?


Muszę być w stanie scalić dwa (bardzo proste) obiekty JavaScript w czasie wykonywania. Na przykład chciałbym:

var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog' }

obj1.merge(obj2);

//obj1 now has three properties: food, car, and animal

Czy ktoś ma skrypt do tego lub zna wbudowany w to sposób? Nie potrzebuję rekursji i nie muszę łączyć funkcji, tylko metody na płaskich obiektach.


1831


pochodzenie


Object.assign .... - caub


Odpowiedzi:


Standardowa metoda ECMAScript 2018

Używałbyś rozprzestrzenianie obiektu:

let merged = {...obj1, ...obj2};

/** There's no limit to the number of objects you can merge.
 *  Later properties overwrite earlier properties with the same name. */
const allRules = {...obj1, ...obj2, ...obj3};

ECMAScript 2015 (ES6) Metoda standardowa

/* For the case in question, you would do: */
Object.assign(obj1, obj2);

/** There's no limit to the number of objects you can merge.
 *  All objects get merged into the first object. 
 *  Only the object in the first argument is mutated and returned.
 *  Later properties overwrite earlier properties with the same name. */
const allRules = Object.assign({}, obj1, obj2, obj3, etc);

(widzieć MDN JavaScript Reference)


Metoda dla ES5 i wcześniejszych

for (var attrname in obj2) { obj1[attrname] = obj2[attrname]; }

Zauważ, że to po prostu doda wszystkie atrybuty obj2 do obj1 które mogą nie być tym, co chcesz, jeśli nadal chcesz używać niezmodyfikowanych obj1.

Jeśli używasz frameworku, który niszczy wszystkie twoje prototypy, musisz stać się bardziej doświadczonym dzięki takim kontrolom hasOwnProperty, ale ten kod zadziała w 99% przypadków.

Przykładowa funkcja:

/**
 * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
 * @param obj1
 * @param obj2
 * @returns obj3 a new object based on obj1 and obj2
 */
function merge_options(obj1,obj2){
    var obj3 = {};
    for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
    for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
    return obj3;
}

1898



To nie działa, jeśli obiekty mają takie same atrybuty nazw, a także chcesz scalić atrybuty. - Xiè Jìléi
To tylko płytka kopia / scalenie. Ma potencjał do oderwania wielu elementów. - Jay Taylor
+1 za uznanie, że niektóre biedne dusze są zmuszone do korzystania z frameworków, które srają ze wszystkich swoich prototypów ... - thejonwithnoh
Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 nie jest to poprawne z przedstawioną implementacją budowania nowego obiektu. - scones
Nie działało to dla mnie z powodu "Dlatego przypisuje właściwości, a jedynie kopiowanie lub definiowanie nowych właściwości. Może to uniemożliwić scalanie nowych właściwości w prototyp, jeśli źródła scalania zawierają moduły pobierające." (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...). Musiałem użyć var merged = {...obj1, ...obj2}. - morgler


jQuery ma również narzędzie do tego: http://api.jquery.com/jQuery.extend/.

Pobrane z dokumentacji jQuery:

// Merge options object into settings object
var settings = { validate: false, limit: 5, name: "foo" };
var options  = { validate: true, name: "bar" };
jQuery.extend(settings, options);

// Now the content of settings object is the following:
// { validate: true, limit: 5, name: "bar" }

Powyższy kod zmutuje plik istniejący obiekt o imieniu settings.


Jeśli chcesz utworzyć nowy obiekt bez modyfikowania żadnego argumentu, użyj tego:

var defaults = { validate: false, limit: 5, name: "foo" };
var options = { validate: true, name: "bar" };

/* Merge defaults and options, without modifying defaults */
var settings = $.extend({}, defaults, options);

// The content of settings variable is now the following:
// {validate: true, limit: 5, name: "bar"}
// The 'defaults' and 'options' variables remained the same.

1115



Gosh, który nazywa się słabo. Jest bardzo mało prawdopodobne, aby go znaleźć podczas wyszukiwania sposobu łączenia obiektów. - Gregory
Ostrożnie: zmienna "ustawienia" zostanie zmieniona. jQuery nie zwraca nowej instancji. Powodem tego (i nazywania) jest to, że .extend () został opracowany w celu rozszerzania obiektów, a nie łączenia rzeczy razem. Jeśli chcesz nowy obiekt (np. Ustawienia domyślne nie chcesz dotykać), zawsze możesz użyć jQuery.extend ({}, ustawienia, opcje); - webmat
Pamiętaj, że jQuery.extend ma również ustawienie głębokie (boolean). jQuery.extend(true,settings,override), co jest ważne, jeśli właściwość w ustawieniach zawiera obiekt, a przesłonięcie ma tylko część tego obiektu. Zamiast usuwać niedopasowane właściwości, ustawienie głębokie zaktualizuje tylko tam, gdzie ono istnieje. Wartością domyślną jest fałsz. - vol7ron
podobnie, jeśli używasz pliku underscore.js, istnieje underscorejs.org/#extend - twmulloy
Gee willikers, naprawdę spisali się dobrze. Jeśli szukasz sposobu na rozszerzenie obiektu Javascript, pojawi się on! Nie możesz tego jednak uderzyć, jeśli próbujesz łączyć ze sobą obiekty JavaScript. - Derek Greer


The Harmony ECMAScript 2015 (ES6) określa Object.assign który to zrobi.

Object.assign(obj1, obj2);

Obecna obsługa przeglądarki to poprawia się, ale jeśli tworzysz dla przeglądarek, które nie mają wsparcia, możesz użyć polyfill.


310



Zauważ, że jest to tylko płytkie scalenie - Joachim Lous
Myślę, że w międzyczasie Object.assign ma całkiem przyzwoity zasięg: kangax.github.io/compat-table/es6/... - LazerBass
To powinna być teraz poprawna odpowiedź. Obecnie ludzie kompilują swój kod, aby był kompatybilny z rzadką przeglądarką (IE11), która nie obsługuje tego rodzaju rzeczy. Uwaga boczna: jeśli nie chcesz dodawać obj2 do obj1, możesz zwrócić nowy obiekt używając Object.assign({}, obj1, obj2) - Duvrai
@Duvrai Według raportów, które widziałem, IE11 zdecydowanie nie jest rzadkością około 18% udziału w rynku od lipca 2016 r. - NanoWizard
@Kermani PO wyraźnie zaznacza, że ​​rekursja nie jest konieczna. Dlatego też, chociaż dobrze jest wiedzieć, że to rozwiązanie wykonuje płytkie scalenie, ta odpowiedź jest wystarczająca. Komentarz JoachimLou otrzymał zasłużone upvotes i zapewnia dodatkowe informacje w razie potrzeby. Myślę, że różnica między głębokim i płytkim łączeniem się i podobnymi tematami wykracza poza zakres tej dyskusji i jest lepiej dopasowana do innych pytań dotyczących SO. - NanoWizard


Wyszukałem kod, aby połączyć właściwości obiektu i znalazłem się tutaj. Ponieważ jednak nie było żadnego kodu do rekurencyjnego scalania, napisałem to sam. (Może rozszerzenie jQuery jest rekurencyjnym BTW?) W każdym razie, mam nadzieję, że ktoś inny również uzna to za przydatne.

(Teraz kod nie używa Object.prototype :)

Kod

/*
* Recursively merge properties of two objects 
*/
function MergeRecursive(obj1, obj2) {

  for (var p in obj2) {
    try {
      // Property in destination object set; update its value.
      if ( obj2[p].constructor==Object ) {
        obj1[p] = MergeRecursive(obj1[p], obj2[p]);

      } else {
        obj1[p] = obj2[p];

      }

    } catch(e) {
      // Property in destination object not set; create it and set its value.
      obj1[p] = obj2[p];

    }
  }

  return obj1;
}

Przykład

o1 = {  a : 1,
        b : 2,
        c : {
          ca : 1,
          cb : 2,
          cc : {
            cca : 100,
            ccb : 200 } } };

o2 = {  a : 10,
        c : {
          ca : 10,
          cb : 20, 
          cc : {
            cca : 101,
            ccb : 202 } } };

o3 = MergeRecursive(o1, o2);

Wytwarza obiekt o3 podobny

o3 = {  a : 10,
        b : 2,
        c : {
          ca : 10,
          cb : 20,
          cc : { 
            cca : 101,
            ccb : 202 } } };

227



Fajnie, ale najpierw zrobię głębię obiektów. W ten sposób zostanie również zmodyfikowana opcja o1, ponieważ obiekty są przekazywane przez odniesienie. - skerit
Tego właśnie szukałem. Upewnij się, aby sprawdzić obj2.hasOwnProperty (p) przed przejściem do instrukcji catch catch. W przeciwnym razie może się zdarzyć, że inne bzdury zostaną scalone z wyższych poziomów w łańcuchu prototypów. - Adam
Dzięki Markus. Zauważ, że o1 jest również modyfikowany przez MergeRecursive, więc na końcu o1 i o3 są identyczne. Może być bardziej intuicyjny, jeśli nic nie zwrócisz, więc użytkownik wie, że to, co dostarcza (o1), zostaje zmodyfikowane i staje się rezultatem. Również w twoim przykładzie warto dodać coś do o2, które nie jest oryginalnie w o1, aby pokazać, że właściwości o2 zostaną wstawione do o1 (ktoś inny poniżej przesłał alternatywny kod, który skopiował Twój przykład, ale ich liczba zrywa się, gdy o2 zawiera właściwości, a nie w o1 - ale twój działa w tym zakresie). - pancake
@JonoRR - samo wywołanie czyni rekursywną: obj1 [p] = MergeRecursive (obj1 [p], obj2 [p]); - z8000
To jest dobra odpowiedź, ale kilka sprzeczek: 1) Żadna logika nie powinna opierać się na wyjątkach; w takim przypadku każdy zgłoszony wyjątek zostanie złapany z nieprzewidywalnymi rezultatami. 2) Zgadzam się z komentarzem @ pancake o nie zwracaniu czegokolwiek, ponieważ oryginalny obiekt jest modyfikowany. Oto przykład jsFiddle bez logiki wyjątku: jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery - Jeff Lowery


Zauważ, że underscore.js„s extend-metoda robi to w jedno-liniowym:

_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}

166



Ten przykład jest dobry, ponieważ masz do czynienia z obiektami anonimowymi, ale jeśli tak nie jest, to webmat komentarz w odpowiedzi jQuery ostrzeganie o mutacjach obowiązuje tutaj, jak również podkreślenie mutuje obiekt docelowy. Podobnie jak w przypadku odpowiedzi jQuery, aby zrobić to bez mutacji, wystarczy połączyć się w pusty obiekt: _({}).extend(obj1, obj2); - Abe Voelker
Jest jeszcze jeden, który może mieć znaczenie w zależności od tego, co próbujesz osiągnąć: _.defaults(object, *defaults) "Wypełnij puste i niezdefiniowane właściwości w obiekcie wartościami z obiektów domyślnych i zwróć obiekt." - conny
Wiele osób skomentowało mutację obiektu docelowego, ale zamiast tworzyć nową metodę, wspólny wzorzec (np. W dojo) to: dojo.mixin (dojo.clone (myobj), {newpropertys ", aby dodać to myobj "}) - Colin D
Podkreślenie może przyjąć dowolną liczbę obiektów, dzięki czemu można uniknąć mutacji oryginalnego obiektu var mergedObj = _.extend({}, obj1, obj2). - lobati


Podobnie do jQuery extend (), masz tę samą funkcję w AngularJS:

// Merge the 'options' object into the 'settings' object
var settings = {validate: false, limit: 5, name: "foo"};
var options  = {validate: true, name: "bar"};
angular.extend(settings, options);

81



możesz także "pożyczyć" definicję rozszerzenia ze źródła JQ ... - juanmf
@juanmf po co? - naXa
@naXa Myślę, że byłaby to opcja, jeśli nie chcesz dodawać biblioteki tylko dla tego fn. - juanmf


Muszę dzisiaj scalić obiekty, a to pytanie (i odpowiedzi) bardzo mi pomogło. Próbowałem niektórych odpowiedzi, ale żadna z nich nie pasowała do moich potrzeb, więc połączyłem niektóre odpowiedzi, dodałem coś osobiście i wymyśliłem nową funkcję scalania. Oto ona:

var merge = function() {
    var obj = {},
        i = 0,
        il = arguments.length,
        key;
    for (; i < il; i++) {
        for (key in arguments[i]) {
            if (arguments[i].hasOwnProperty(key)) {
                obj[key] = arguments[i][key];
            }
        }
    }
    return obj;
};

Niektóre przykłady użycia:

var t1 = {
    key1: 1,
    key2: "test",
    key3: [5, 2, 76, 21]
};
var t2 = {
    key1: {
        ik1: "hello",
        ik2: "world",
        ik3: 3
    }
};
var t3 = {
    key2: 3,
    key3: {
        t1: 1,
        t2: 2,
        t3: {
            a1: 1,
            a2: 3,
            a4: [21, 3, 42, "asd"]
        }
    }
};

console.log(merge(t1, t2));
console.log(merge(t1, t3));
console.log(merge(t2, t3));
console.log(merge(t1, t2, t3));
console.log(merge({}, t1, { key1: 1 }));

59



To jest świetna odpowiedź! To jest tak jak kod źródłowy jQuery! - Progo
Jedynym tagiem na pytanie jest javascript, więc jest to jedyna poprawna odpowiedź, a także świetna. - skobaljic
dobra odpowiedź, ale myślę, że jeśli właściwość jest obecna w pierwszym i drugim, a próbujesz utworzyć nowy obiekt poprzez scalenie pierwszego i drugiego, to unikatowe obiekty obecne w pierwszym obiekcie zostaną utracone - Strikers
Ale czy nie jest to oczekiwane zachowanie? Celem tej funkcji jest przesłonięcie wartości w kolejności argumentów. Następny zastąpi aktualny. Jak zachować unikalne wartości? Z mojego przykładu, czego oczekujesz merge({}, t1, { key1: 1 })? - Emre Erkan
Oczekuję, że scalę tylko wartości nieopisane do obiektu. - AGamePlayer


Możesz użyć właściwości rozprzestrzeniania obiektu- obecnie propozycja ECMAScript na etapie 3.

const obj1 = { food: 'pizza', car: 'ford' };
const obj2 = { animal: 'dog' };

const obj3 = { ...obj1, ...obj2 };
console.log(obj3);


43



Obsługa przeglądarki? - Alph.Dev
@ Alph.Dev Czy znasz caniuse.com? - connexo
@connexo W jakim celu o to pytasz? Zapewnij odpowiednie łącze do właściwości spreadu obiektów JS na stronie caniuse.com lub nie przeszkadzaj. To nie jest miejsce, w którym można grać mądrą dupę. - Alph.Dev
W szczególności nie jest to miejsce, w którym można poprosić o rzeczy, które każdy może łatwo sprawdzić samodzielnie. - connexo
Nie mogę uzyskać składni 3 kropek do pracy w node.js. węzeł narzeka na to. - blackhawk