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


2517

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 do tego skrypt lub wie o wbudowanym sposobie, aby to zrobić? Nie potrzebuję rekurencji i nie muszę scalać funkcji, tylko metody na płaskich obiektach.


Warto zwrócić uwagę na tę odpowiedź na podobne pytanie , które pokazuje, jak połączyć „jeden poziom w dół”. Oznacza to, że łączy wartości duplikatów kluczy (zamiast nadpisywania pierwszej wartości drugą wartością), ale nie powtarza się dalej. IMHO, to dobry czysty kod do tego zadania.
ToolmakerSteve

BTW, kilka pierwszych odpowiedzi wykonuje scalanie „płytkie”: jeśli ten sam klucz istnieje zarówno w obj1, jak i obj2, wartość w obj2 zostaje zachowana, wartość w obj1 jest odrzucana. Np. Jeśli miałby to przykład pytania var obj2 = { animal: 'dog', food: 'bone' };, połączenie byłoby { food: 'bone', car: 'ford', animal: 'dog' }. Jeśli pracujesz z „zagnieżdżonymi danymi” i chcesz „głębokiego scalenia”, poszukaj odpowiedzi, które wspominają o „głębokim scaleniu” lub „rekurencji”. Jeśli masz wartości arrays, użyj opcji „arrayMerge” github „TehShrike / deepmerge”, jak wspomniano tutaj .
ToolmakerSteve,

Postępuj zgodnie z poniższym linkiem stackoverflow.com/questions/171251/... Operator spreadu, który jest nową funkcją w wersji ES6
Dev216

Odpowiedzi:


2904

ECMAScript 2018 Standard Method

Byłoby użyć obiektu rozprzestrzeniania :

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

mergedjest teraz unią obj1i obj2. Właściwości w obj2nadpisze te w obj1.

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

Oto także dokumentacja MDN dla tej składni. Jeśli używasz babel, potrzebujesz wtyczki babel-plugin-transform-object-rest-spread-spread , aby działała.

ECMAScript 2015 (ES6) Standardowa metoda

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

(patrz MDN JavaScript Reference )


Metoda dla ES5 i wcześniejszych

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

Zauważ, że to będzie po prostu dodać wszystkie atrybuty obj2do obj1których nie może być to, co chcesz, jeśli nadal chcesz używać niemodyfikowana obj1.

Jeśli używasz frameworka, który psuje wszystkie prototypy, musisz uzyskać bardziej wyszukane kontrole hasOwnProperty, ale ten kod będzie dział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;
}

90
To nie działa, jeśli obiekty mają takie same atrybuty nazw, a Ty też chcesz scalić atrybuty.
Xiè Jìléi

69
To tylko płytkie kopiowanie / scalanie. Może potencjalnie zlikwidować wiele elementów.
Jay Taylor,

45
+1 za potwierdzenie, że niektóre biedne dusze są zmuszone używać szkieletów, które
psują

4
Nie działało to dla mnie z powodu „Dlatego przypisuje właściwości zamiast kopiowania lub definiowania nowych właściwości. Może to uniemożliwić scalanie nowych właściwości w prototypie, jeśli źródła scalania zawierają gettery”. ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ). Musiałem użyć var merged = {...obj1, ...obj2}.
morgler

2
@ Ze'ev{...obj1, ...{animal: 'dog'}}
backslash112

1190

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

Zaczerpnięte 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 spowoduje mutację istniejącego obiektu o nazwie settings.


Jeśli chcesz utworzyć nowy obiekt bez modyfikowania żadnego z argumentów, 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.

166
Uwaga: zmienne „ustawienia” zostaną jednak zmodyfikowane. jQuery nie zwraca nowej instancji. Powodem tego (i nazewnictwa) jest to, że .extend () został opracowany w celu rozszerzania obiektów, a nie łączenia ich razem. Jeśli chcesz nowego obiektu (np. Ustawienia są domyślne, których nie chcesz dotykać), zawsze możesz jQuery.extend ({}, ustawienia, opcje);
webmat

1
Tak jest! Wielkie dzięki. Jak stwierdzono w poprzednim komentarzu, jest źle nazwany. Przeszukałem jQuery docs z powrotem na czwartym miejscu i nie napotkałem na to.
Mike Starov

28
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 zastąpienie ma tylko część tego obiektu. Zamiast usuwać niedopasowane właściwości, głębokie ustawienie zostanie zaktualizowane tylko tam, gdzie istnieje. Wartość domyślna to false.
vol7ron

19
Rany Willikers, naprawdę wykonali dobrą robotę, nazywając to. Jeśli szukasz sposobu na rozszerzenie obiektu JavaScript, pojawia się od razu! Możesz jednak nie trafić na to, jeśli próbujesz połączyć lub połączyć obiekty JavaScript.
Derek Greer

2
Nie mów ludziom, aby zastanawiali się nad zastosowaniem metody bibliotecznej, gdy kilka linii waniliowego JS zrobi to, co chce. Był czas zanim jQuery!
CrazyMerlin,

346

Harmony ECMAScript 2015 (ES6) określa, Object.assignktóry będzie to zrobić.

Object.assign(obj1, obj2);

Obecna obsługa przeglądarek jest coraz lepsza , ale jeśli tworzysz przeglądarki, które nie obsługują tej funkcji, możesz skorzystać z funkcji wypełniania .


35
Zauważ, że jest to tylko płytkie połączenie
Joachim Lous,

Myślę, że tymczasem Object.assignma całkiem przyzwoity zasięg: kangax.github.io/compat-table/es6/…
LazerBass

13
To powinna być teraz poprawna odpowiedź. Obecnie ludzie kompilują swój kod, aby był zgodny z rzadką przeglądarką (IE11), która nie obsługuje tego rodzaju rzeczy. Uwaga dodatkowa: jeśli nie chcesz dodawać obj2 do obj1, możesz zwrócić nowy obiekt za pomocąObject.assign({}, obj1, obj2)
Duvrai

6
@Duvrai Według raportów, które widziałem, IE11 zdecydowanie nie jest rzadkie i stanowi około 18% udziału w rynku w lipcu 2016 r.
NanoWizard

3
@Kermani PO wyraźnie wskazuje, że rekurencja nie jest konieczna. Dlatego, chociaż warto wiedzieć, że to rozwiązanie wykonuje płytkie scalenie, odpowiedź ta jest wystarczająca. Komentarz JoachimLou otrzymał zasłużone głosy poparcia i zawiera dodatkowe informacje w razie potrzeby. Myślę, że różnica między głębokim a płytkim łączeniem i podobnymi tematami wykracza poza zakres tej dyskusji i lepiej pasują do innych pytań SO.
NanoWizard,

264

Poszukałem kodu, aby scalić właściwości obiektu i znalazłem się tutaj. Ponieważ jednak nie było żadnego kodu do scalania rekurencyjnego, napisałem go sam. (Może rozszerzenie jQuery jest rekurencyjne BTW?) W każdym razie, mam nadzieję, że komuś innemu również się przyda.

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

Tworzy obiekt o3 jak

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

11
Fajnie, ale najpierw zrobię głęboką kopię obiektów. W ten sposób o1 również zostałby zmodyfikowany, ponieważ obiekty są przekazywane przez referencję.
skerit

1
Tego właśnie szukałem. Przed przejściem do instrukcji try catch należy sprawdzić, czy obj2.hasOwnProperty (p). W przeciwnym razie możesz skończyć z innym badziewiem łączącym się z wyższego poziomu w łańcuchu prototypów.
Adam

3
Dzięki Markus. Zauważ, że o1 jest również modyfikowane 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 są wstawiane do o1 (ktoś inny poniżej przesłał alternatywny kod kopiujący twój przykład, ale ich łamie się, gdy o2 nie zawiera właściwości w o1 - ale twój działa w tym zakresie).
naleśnik

7
To dobra odpowiedź, ale kilka sporów: 1) Żadna logika nie powinna opierać się na wyjątkach; w takim przypadku zgłoszony wyjątek zostanie przechwycony z nieprzewidywalnymi wynikami. 2) Zgadzam się z komentarzem @ pancake, że niczego nie zwracam, ponieważ oryginalny obiekt został zmodyfikowany. Oto przykład jsFiddle bez logiki wyjątku: jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery
Jeff Lowery

3
Ponadto obj2 [p] .constructor == Tablica jest potrzebna w każdym przypadku, gdy istnieje tablica obiektów.
Kompozytor,

175

Zauważ, że underscore.js's extend-method robi to w jednej linijce:

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

36
Ten przykład jest dobry, ponieważ masz do czynienia z obiektami anonimowymi, ale jeśli tak nie jest, to tutaj stosuje się komentarz webmat w odpowiedzi jQuery ostrzegającej o mutacjach, ponieważ podkreślenie powoduje również mutację obiektu docelowego . Jak odpowiedź jQuery, aby zrobić tą mutacją wolne tylko seryjnej do pustego obiektu:_({}).extend(obj1, obj2);
Abe Voelker

8
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 mutowanie obiektu docelowego, ale zamiast metody tworzenia nowego, powszechnym wzorcem (na przykład w dojo) jest powiedzenie dojo.mixin (dojo.clone (myobj), {newpropertys: ", aby dodać do myobj "})
Colin D

7
Podkreślenie może przyjmować dowolną liczbę obiektów, dzięki czemu można uniknąć mutacji oryginalnego obiektu var mergedObj = _.extend({}, obj1, obj2).
lobati

84

Podobnie jak w jQuery ext (), 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);

1
@naXa Myślę, że byłaby to opcja, jeśli nie chcesz dodawać lib tylko dla tego fn.
juanmf

67

Muszę dziś scalać 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, sam coś dodałem i wymyśliłem nową funkcję scalania. Oto on:

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ładowe zastosowania:

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

3
dobra odpowiedź, ale myślę, że jeśli właściwość jest obecna w pierwszej i drugiej, a ty próbujesz stworzyć nowy obiekt, łącząc najpierw i drugą, wówczas unikane będą te znajdujące się w pierwszym obiekcie
Strikers

2
Ale czy nie jest to oczekiwane zachowanie? Celem tej funkcji jest zastępowanie wartości w kolejności argumentów. Następny zastąpi prąd. Jak możesz zachować unikalne wartości? Z mojego przykładu, czego oczekujesz merge({}, t1, { key1: 1 })?
Emre Erkan,

Oczekuję, że scalę tylko wartości nietypowe.
AGamePlayer

nie powiedzie się, jeśli oczekiwany wynik jest następujący: gist.github.com/ceremcem/a54f90923c6e6ec42c7daf24cf2768bd
ceremcem

To działało dla mnie w trybie ścisłym dla mojego projektu node.js. Jak dotąd jest to najlepsza odpowiedź w tym poście.
klewis


41

Podane rozwiązania powinny zostać zmodyfikowane, aby sprawdzić source.hasOwnProperty(property)w for..inpętlach przed przypisaniem - w przeciwnym razie kończy się kopiowanie właściwości całego łańcucha prototypów, co jest rzadko pożądane ...


40

Scal właściwości N obiektów w jednym wierszu kodu

Object.assignMetoda jest częścią standardu (2015) ES6 ECMAScript i robi dokładnie to, czego potrzebujesz. ( IEnie obsługiwane)

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

Metoda Object.assign () służy do kopiowania wartości wszystkich możliwych do wyliczenia własnych właściwości z jednego lub więcej obiektów źródłowych do obiektu docelowego.

Czytaj więcej...

Polyfill do obsługi starszych przeglądarek:

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

co „surowe” robi w starszych przeglądarkach lub dlaczego w ogóle istnieje. przeglądarki obsługujące obiekt [definProperty; Klucze; getOwnPropertyDescriptor; itp.], nie są dokładnie stare. To tylko wcześniejsze wersje UA 5 generacji.
Bekim Bacaj,

Dziękujemy za wyjaśnienie, że Objects.assignzwraca scalony obiekt, którego inne odpowiedzi nie zawierają. To bardziej funkcjonalny styl programowania.
Sridhar Sarnobat

36

Następujące dwa są prawdopodobnie dobrym punktem wyjścia. lodash ma również funkcję dostosowywania do tych specjalnych potrzeb!

_.extend( http://underscorejs.org/#extend )
_.merge( https://lodash.com/docs#merge )


4
Należy pamiętać, że powyższe metody mutują oryginalny obiekt.
Wtower

Metoda lodash zadziałała dla mnie, ponieważ chciałem scalić dwa obiekty i zachować właściwości zagnieżdżone na głębokości większej niż jeden lub dwa poziomy (zamiast tylko ich zamiany / czyszczenia).
SamBrick

true @Wtower. Właśnie z tego powodu miałem błąd. Najlepiej upewnij się, że zawsze zaczyna się jak _.merge ({}, ...
Martin Cremer,

29

Nawiasem mówiąc, wszystko, co robisz, to nadpisywanie właściwości, a nie łączenie ...

W ten sposób naprawdę scalono obszar obiektów JavaScript: tylko klucze w toobiekcie, które same nie są obiektami, zostaną zastąpione przez from. Wszystko inne zostanie naprawdę połączone . Oczywiście możesz zmienić to zachowanie, aby nie zastępować niczego, co istnieje, jak tylko wtedy to[n] is undefined, gdy itd ...:

var realMerge = function (to, from) {

    for (n in from) {

        if (typeof to[n] != 'object') {
            to[n] = from[n];
        } else if (typeof from[n] == 'object') {
            to[n] = realMerge(to[n], from[n]);
        }
    }
    return to;
};

Stosowanie:

var merged = realMerge(obj1, obj2);

3
Bardzo mi
pomogło,

1
Warto zauważyć, że tablica typeof i typeof null zwrócą „obiekt” i zepsują to.
Salakar,

@ u01jmg3 patrz moja odpowiedź tutaj: stackoverflow.com/questions/27936772/… - funkcja
isObject

Zrywa się, jeśli używasz jest w trybie ścisłym dla projektów
node.js

28

Oto moje dźgnięcie, które

  1. Obsługuje głębokie scalanie
  2. Nie mutuje argumentów
  3. Przyjmuje dowolną liczbę argumentów
  4. Nie rozszerza prototypu obiektu
  5. Nie zależy od innej biblioteki ( jQuery , MooTools , Underscore.js itp.)
  6. Obejmuje sprawdzenie hasOwnProperty
  7. Jest krótki :)

    /*
        Recursively merge properties and return new object
        obj1 &lt;- obj2 [ &lt;- ... ]
    */
    function merge () {
        var dst = {}
            ,src
            ,p
            ,args = [].splice.call(arguments, 0)
        ;
    
        while (args.length > 0) {
            src = args.splice(0, 1)[0];
            if (toString.call(src) == '[object Object]') {
                for (p in src) {
                    if (src.hasOwnProperty(p)) {
                        if (toString.call(src[p]) == '[object Object]') {
                            dst[p] = merge(dst[p] || {}, src[p]);
                        } else {
                            dst[p] = src[p];
                        }
                    }
                }
            }
        }
    
       return dst;
    }

Przykład:

a = {
    "p1": "p1a",
    "p2": [
        "a",
        "b",
        "c"
    ],
    "p3": true,
    "p5": null,
    "p6": {
        "p61": "p61a",
        "p62": "p62a",
        "p63": [
            "aa",
            "bb",
            "cc"
        ],
        "p64": {
            "p641": "p641a"
        }
    }
};

b = {
    "p1": "p1b",
    "p2": [
        "d",
        "e",
        "f"
    ],
    "p3": false,
    "p4": true,
    "p6": {
        "p61": "p61b",
        "p64": {
            "p642": "p642b"
        }
    }
};

c = {
    "p1": "p1c",
    "p3": null,
    "p6": {
        "p62": "p62c",
        "p64": {
            "p643": "p641c"
        }
    }
};

d = merge(a, b, c);


/*
    d = {
        "p1": "p1c",
        "p2": [
            "d",
            "e",
            "f"
        ],
        "p3": null,
        "p5": null,
        "p6": {
            "p61": "p61b",
            "p62": "p62c",
            "p63": [
                "aa",
                "bb",
                "cc"
            ],
            "p64": {
                "p641": "p641a",
                "p642": "p642b",
                "p643": "p641c"
            }
        },
        "p4": true
    };
*/

W porównaniu z innymi, które testowałem na tej stronie, ta funkcja jest naprawdę rekurencyjna (wykonuje głębokie scalanie) i naśladuje to, co jQuery.extend () robi naprawdę dobrze. Lepiej byłoby jednak zmodyfikować pierwszy obiekt / argument, aby użytkownik mógł zdecydować, czy chce przekazać pusty obiekt {}jako pierwszy parametr, czy też funkcję zmodyfikować oryginalny obiekt. Dlatego jeśli zmienisz dst = {}na dst = arguments[0]to, zmieni się pierwszy obiekt, który przekazujesz do scalonego obiektu
Jasdeep Khalsa

3
Teraz przestał działać w chrome. Myślę, że to zły pomysł, aby używać takiej konstrukcji toString.call(src) == '[object Object]'do sprawdzania, czy jest jakiś przedmiot, czy nie. typeof srcjest znacznie lepszy. Co więcej, przekształca tablice w obiekty (przynajmniej takie zachowanie mam na najnowszym chrome).
m03geek

Musiałem to zrobić wewnątrz chciwej pętli, porównałem tę funkcję z tą, której do tej pory używałem _.merge (obiekt, [źródła]) z lodash (rodzaj lib podkreślenia): twoja funkcja jest prawie dwa razy szybsza dzięki stary.
Arnaud Bouchot,

20

Object.assign ()

ECMAScript 2015 (ES6)

Jest to nowa technologia, będąca częścią standardu ECMAScript 2015 (ES6). Specyfikacja tej technologii została sfinalizowana, ale sprawdź w tabeli kompatybilności status użytkowania i implementacji w różnych przeglądarkach.

Metoda Object.assign () służy do kopiowania wartości wszystkich możliwych do wyliczenia własnych właściwości z jednego lub więcej obiektów źródłowych do obiektu docelowego. Zwróci obiekt docelowy.

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, target object itself is changed.

19

W przypadku niezbyt skomplikowanych obiektów możesz użyć JSON :

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

objMerge = JSON.stringify(obj1) + JSON.stringify(obj2);

// {"food": "pizza","car":"ford"}{"animal":"dog","car":"chevy"}

objMerge = objMerge.replace(/\}\{/, ","); //  \_ replace with comma for valid JSON

objMerge = JSON.parse(objMerge); // { food: 'pizza', animal: 'dog', car: 'chevy'}
// Of same keys in both objects, the last object's value is retained_/

Pamiętaj, że w tym przykładzie „} {” nie może występować w ciągu znaków!


4
var so1 = JSON.stringify(obj1); var so2 = JSON.stringify(obj1); objMerge = so1.substr(0, so1.length-1)+","+so2.substr(1); console.info(objMerge);
Quamis

function merge(obj1, obj2) { return JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)) .replace(/\}\{/g, ',').replace(/,\}/g, '}').replace(/\{,/g, '{')); }
Alex Ivasyuv

lub jako jedna linijka:objMerge = JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)).replace(/\}\{/, ", "));
Rabin Shuki Gur

1
@Charles, przepraszam, ale jak to jest „ta metoda bardzo niebezpieczna”, ale także zabawna - jest to najbezpieczniejsze możliwe. Po pierwsze używa JSON do ciężkiego podnoszenia odczytu zawartości obiektu, po drugie używa obiektu JSON, który jest pod każdym względem najbezpieczniejszy zoptymalizowany pod kątem maszyny do ponownej analizy JSO z literału ciągu obiektowego zgodnego z JSON. Jest także wstecznie kompatybilny z IE8, która jest pierwszą przeglądarką, która wdrożyła swój standard. To sprawia, że ​​zastanawiam się „dlaczego och, dlaczego musiałeś powiedzieć tak głupią rzecz i uciec od tego”?
Bekim Bacaj

Wierzę, że funkcje w obiekcie ulegną tutaj awarii, ponieważ nie można użyć funkcji stringify, ale czy możemy wyjaśnić inne wady?
yeahdixon

18

Istnieje biblioteka nazywa deepmergena GitHub : To wydaje się być coraz pewną trakcję. Jest samodzielny, dostępny zarówno za pośrednictwem menedżerów pakietów npm, jak i altana.

Byłbym skłonny użyć tego lub ulepszyć to zamiast wklejać kod z odpowiedzi.


17

Najlepszym sposobem na to jest dodanie właściwej właściwości, której nie można wyliczyć za pomocą Object.defineProperty.

W ten sposób będziesz w stanie iterować właściwości obiektów bez nowo utworzonego „rozszerzenia”, które uzyskałbyś, gdybyś utworzył właściwość za pomocą Object.prototype.extend.

Mam nadzieję, że to pomaga:

Object.defineProperty (Object.prototype, „przedłużyć”, {
    wyliczalny: fałsz,
    wartość: funkcja (z) {
        var props = Object.getOwnPropertyNames (from);
        var dest = this;
        props.forEach (funkcja (nazwa) {
            if (name in dest) {
                var destination = Object.getOwnPropertyDescriptor (z, nazwa);
                Object.defineProperty (dest, nazwa, miejsce docelowe);
            }
        });
        zwróć to;
    }
});

Gdy już to zrobisz, możesz:

var obj = {
    nazwa: „stos”,
    zakończenie: „przepełnienie”
}
var replace = {
    nazwa: „stock”
};

obj.extend (zamiennik);

Właśnie napisałem o tym post na blogu: http://onemoredigit.com/post/1527191998/extending-objects-in-node-js


Chwileczkę --- kod nie działa! :( wstawił go do jsperf w celu porównania z innymi algorytmami scalającymi, a kod zawodzi - rozszerzenie funkcji nie istnieje
Jens Roland

16

Możesz po prostu użyć jQuery extend

var obj1 = { val1: false, limit: 5, name: "foo" };
var obj2 = { val2: true, name: "bar" };

jQuery.extend(obj1, obj2);

Teraz obj1zawiera wszystkie wartości obj1iobj2


2
obj1 będzie zawierać wszystkie wartości, a nie obj2. Rozszerzasz pierwszy obiekt. jQuery.extend( target [, object1 ] [, objectN ] )
ruuter

5
To pytanie nie dotyczy jQuery. JavaScript! = JQuery
Jorge Riv

1
Poszukiwałem JavaScript, ale jest to prostsze podejście
Ehsan

13

Prototyp ma to:

Object.extend = function(destination,source) {
    for (var property in source)
        destination[property] = source[property];
    return destination;
}

obj1.extend(obj2) zrobi co chcesz.


6
Miałeś na myśli Object.prototype.extend? W każdym razie nie jest dobrym pomysłem rozszerzenie Object.prototype. -1.
SolutionYogi

Pomyśl o tym, prawdopodobnie powinno być, Object.prototypea nie Object... Dobry pomysł, czy nie, to jest to, co prototype.jsrobi framework, więc jeśli go używasz, lub inny framework, który od niego zależy, Object.prototypebędzie już rozszerzony extend.
ephemient

2
Nie zamierzam cię narzekać, ale mówienie, że jest w porządku, ponieważ robi to prototype.js, jest kiepskim argumentem. Prototype.js jest popularny, ale to nie znaczy, że są wzorem do naśladowania w JavaScript. Sprawdź tę szczegółową recenzję biblioteki Prototype przez profesjonalistę JS. dhtmlkitchen.com/?category=/JavaScript/&date=2008/06/17/…
SolutionYogi

7
Kogo to obchodzi, czy to dobrze, czy źle? Jeśli to działa, to działa. Czekanie na idealne rozwiązanie jest świetne, z wyjątkiem prób utrzymania kontraktu, pozyskania nowych klientów lub dotrzymania terminów. Daj wszystkim, którzy pasjonują się programowaniem darmowych pieniędzy, a ostatecznie zrobią wszystko „we właściwy sposób”.
David,

1
Gdzie wszyscy znaleźliście Object.prototype? Object.extendnie będzie zakłócać twoich obiektów, po prostu dołącza funkcję do Objectobiektu. I obj1.extend(obj2)nie jest poprawnym sposobem na uruchomienie funkcji, użyj Object.extend(obj1, obj2)insted
artnikpro

13

** Scalanie obiektów jest proste w użyciu Object.assignlub ...operator rozkładania **

var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog', car: 'BMW' }
var obj3 = {a: "A"}


var mergedObj = Object.assign(obj1,obj2,obj3)
 // or using the Spread operator (...)
var mergedObj = {...obj1,...obj2,...obj3}

console.log(mergedObj);

Obiekty są scalane od prawej do lewej, co oznacza, że ​​obiekty, które mają identyczne właściwości jak obiekty po ich prawej stronie, zostaną przesłonięte.

W tym przykładzie obj2.carzastępujeobj1.car


12

Tylko jeśli ktoś korzysta z Google Closure Library :

goog.require('goog.object');
var a = {'a': 1, 'b': 2};
var b = {'b': 3, 'c': 4};
goog.object.extend(a, b);
// Now object a == {'a': 1, 'b': 3, 'c': 4};

Podobna funkcja pomocnicza istnieje dla tablicy :

var a = [1, 2];
var b = [3, 4];
goog.array.extend(a, b); // Extends array 'a'
goog.array.concat(a, b); // Returns concatenation of array 'a' and 'b'

10

Rozszerzyłem metodę Davida Coalliera:

  • Dodano możliwość łączenia wielu obiektów
  • Obsługuje głębokie obiekty
  • parametr przesłonięcia (jest wykrywany, jeśli ostatnim parametrem jest wartość logiczna)

Jeśli zastąpienie jest fałszywe, żadna właściwość nie zostanie zastąpiona, ale zostaną dodane nowe właściwości.

Zastosowanie: obj.merge (scala ... [, zastępuj]);

Oto mój kod:

Object.defineProperty(Object.prototype, "merge", {
    enumerable: false,
    value: function () {
        var override = true,
            dest = this,
            len = arguments.length,
            props, merge, i, from;

        if (typeof(arguments[arguments.length - 1]) === "boolean") {
            override = arguments[arguments.length - 1];
            len = arguments.length - 1;
        }

        for (i = 0; i < len; i++) {
            from = arguments[i];
            if (from != null) {
                Object.getOwnPropertyNames(from).forEach(function (name) {
                    var descriptor;

                    // nesting
                    if ((typeof(dest[name]) === "object" || typeof(dest[name]) === "undefined")
                            && typeof(from[name]) === "object") {

                        // ensure proper types (Array rsp Object)
                        if (typeof(dest[name]) === "undefined") {
                            dest[name] = Array.isArray(from[name]) ? [] : {};
                        }
                        if (override) {
                            if (!Array.isArray(dest[name]) && Array.isArray(from[name])) {
                                dest[name] = [];
                            }
                            else if (Array.isArray(dest[name]) && !Array.isArray(from[name])) {
                                dest[name] = {};
                            }
                        }
                        dest[name].merge(from[name], override);
                    } 

                    // flat properties
                    else if ((name in dest && override) || !(name in dest)) {
                        descriptor = Object.getOwnPropertyDescriptor(from, name);
                        if (descriptor.configurable) {
                            Object.defineProperty(dest, name, descriptor);
                        }
                    }
                });
            }
        }
        return this;
    }
});

Przykłady i przypadki testowe:

function clone (obj) {
    return JSON.parse(JSON.stringify(obj));
}
var obj = {
    name : "trick",
    value : "value"
};

var mergeObj = {
    name : "truck",
    value2 : "value2"
};

var mergeObj2 = {
    name : "track",
    value : "mergeObj2",
    value2 : "value2-mergeObj2",
    value3 : "value3"
};

assertTrue("Standard", clone(obj).merge(mergeObj).equals({
    name : "truck",
    value : "value",
    value2 : "value2"
}));

assertTrue("Standard no Override", clone(obj).merge(mergeObj, false).equals({
    name : "trick",
    value : "value",
    value2 : "value2"
}));

assertTrue("Multiple", clone(obj).merge(mergeObj, mergeObj2).equals({
    name : "track",
    value : "mergeObj2",
    value2 : "value2-mergeObj2",
    value3 : "value3"
}));

assertTrue("Multiple no Override", clone(obj).merge(mergeObj, mergeObj2, false).equals({
    name : "trick",
    value : "value",
    value2 : "value2",
    value3 : "value3"
}));

var deep = {
    first : {
        name : "trick",
        val : "value"
    },
    second : {
        foo : "bar"
    }
};

var deepMerge = {
    first : {
        name : "track",
        anotherVal : "wohoo"
    },
    second : {
        foo : "baz",
        bar : "bam"
    },
    v : "on first layer"
};

assertTrue("Deep merges", clone(deep).merge(deepMerge).equals({
    first : {
        name : "track",
        val : "value",
        anotherVal : "wohoo"
    },
    second : {
        foo : "baz",
        bar : "bam"
    },
    v : "on first layer"
}));

assertTrue("Deep merges no override", clone(deep).merge(deepMerge, false).equals({
    first : {
        name : "trick",
        val : "value",
        anotherVal : "wohoo"
    },
    second : {
        foo : "bar",
        bar : "bam"
    },
    v : "on first layer"
}));

var obj1 = {a: 1, b: "hello"};
obj1.merge({c: 3});
assertTrue(obj1.equals({a: 1, b: "hello", c: 3}));

obj1.merge({a: 2, b: "mom", d: "new property"}, false);
assertTrue(obj1.equals({a: 1, b: "hello", c: 3, d: "new property"}));

var obj2 = {};
obj2.merge({a: 1}, {b: 2}, {a: 3});
assertTrue(obj2.equals({a: 3, b: 2}));

var a = [];
var b = [1, [2, 3], 4];
a.merge(b);
assertEquals(1, a[0]);
assertEquals([2, 3], a[1]);
assertEquals(4, a[2]);


var o1 = {};
var o2 = {a: 1, b: {c: 2}};
var o3 = {d: 3};
o1.merge(o2, o3);
assertTrue(o1.equals({a: 1, b: {c: 2}, d: 3}));
o1.b.c = 99;
assertTrue(o2.equals({a: 1, b: {c: 2}}));

// checking types with arrays and objects
var bo;
a = [];
bo = [1, {0:2, 1:3}, 4];
b = [1, [2, 3], 4];

a.merge(b);
assertTrue("Array stays Array?", Array.isArray(a[1]));

a = [];
a.merge(bo);
assertTrue("Object stays Object?", !Array.isArray(a[1]));

a = [];
a.merge(b);
a.merge(bo);
assertTrue("Object overrides Array", !Array.isArray(a[1]));

a = [];
a.merge(b);
a.merge(bo, false);
assertTrue("Object does not override Array", Array.isArray(a[1]));

a = [];
a.merge(bo);
a.merge(b);
assertTrue("Array overrides Object", Array.isArray(a[1]));

a = [];
a.merge(bo);
a.merge(b, false);
assertTrue("Array does not override Object", !Array.isArray(a[1]));

Moja metoda równości znajduje się tutaj: Porównanie obiektów w JavaScript



9

W Ext JS 4 można to zrobić w następujący sposób:

var mergedObject = Ext.Object.merge(object1, object2)

// Or shorter:
var mergedObject2 = Ext.merge(object1, object2)

Zobacz scalanie (obiekt): Obiekt .


1
Uważaj na błąd EXTJS-9173 w EXT.Object.merge na razie po +2 latach wciąż nierozwiązanych
Chris

9

Wow ... to pierwszy post StackOverflow, który widziałem na wielu stronach. Przepraszamy za dodanie kolejnej „odpowiedzi”

Ta metoda jest dla ES5 i wcześniejszych - istnieje wiele innych odpowiedzi dotyczących ES6.

Nie widziałem żadnego „głębokiego” obiektu łączącego się za pomocą tej argumentswłaściwości. Oto moja odpowiedź - kompaktowa i rekurencyjna , pozwalająca na przekazywanie nieograniczonej liczby argumentów obiektowych:

function extend() {
    for (var o = {}, i = 0; i < arguments.length; i++) {
        // if (arguments[i].constructor !== Object) continue;
        for (var k in arguments[i]) {
            if (arguments[i].hasOwnProperty(k)) {
                o[k] = arguments[i][k].constructor === Object ? extend(o[k] || {}, arguments[i][k]) : arguments[i][k];
            }
        }
    }
    return o;
}

Skomentowana część jest opcjonalna. Po prostu pominie przekazane argumenty, które nie są obiektami (zapobiegając błędom).

Przykład:

extend({
    api: 1,
    params: {
        query: 'hello'
    }
}, {
    params: {
        query: 'there'
    }
});

// outputs {api: 1, params: {query: 'there'}}

Ta odpowiedź jest teraz kroplą w oceanie ...


Najkrótsza odpowiedź, która działa świetnie! Zasługuje na więcej pochwał!
Jens Törnell

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

// result
result: {food: "pizza", car: "ford", animal: "dog"}

Korzystanie z jQuery.extend () - Link

// Merge obj1 & obj2 to result
var result1 = $.extend( {}, obj1, obj2 );

Korzystanie z _.merge () - Link

// Merge obj1 & obj2 to result
var result2 = _.merge( {}, obj1, obj2 );

Korzystanie z _.extend () - Link

// Merge obj1 & obj2 to result
var result3 = _.extend( {}, obj1, obj2 );

Korzystanie z Object.assign () ECMAScript 2015 (ES6) - Link

// Merge obj1 & obj2 to result
var result4 = Object.assign( {}, obj1, obj2 );

Wyjście wszystkich

obj1: { animal: 'dog' }
obj2: { food: 'pizza', car: 'ford' }
result1: {food: "pizza", car: "ford", animal: "dog"}
result2: {food: "pizza", car: "ford", animal: "dog"}
result3: {food: "pizza", car: "ford", animal: "dog"}
result4: {food: "pizza", car: "ford", animal: "dog"}

7

Na podstawie odpowiedzi Markusa i vsync jest to wersja rozszerzona. Funkcja przyjmuje dowolną liczbę argumentów. Można go użyć do ustawienia właściwości w DOM węzłach i wykonywania głębokich kopii wartości. Jednak pierwszy argument podano przez odniesienie.

Aby wykryć węzeł DOM, używana jest funkcja isDOMNode () (patrz Pytanie o przepełnieniu stosu JavaScript isDOM - Jak sprawdzić, czy obiekt JavaScript jest obiektem DOM? )

Został przetestowany w Opera 11, Firefox 6, Internet Explorer 8 i Google Chrome 16.

Kod

function mergeRecursive() {

  // _mergeRecursive does the actual job with two arguments.
  var _mergeRecursive = function (dst, src) {
    if (isDOMNode(src) || typeof src !== 'object' || src === null) {
      return dst;
    }

    for (var p in src) {
      if (!src.hasOwnProperty(p))
        continue;
      if (src[p] === undefined)
        continue;
      if ( typeof src[p] !== 'object' || src[p] === null) {
        dst[p] = src[p];
      } else if (typeof dst[p]!=='object' || dst[p] === null) {
        dst[p] = _mergeRecursive(src[p].constructor===Array ? [] : {}, src[p]);
      } else {
        _mergeRecursive(dst[p], src[p]);
      }
    }
    return dst;
  }

  // Loop through arguments and merge them into the first argument.
  var out = arguments[0];
  if (typeof out !== 'object' || out === null)
    return out;
  for (var i = 1, il = arguments.length; i < il; i++) {
    _mergeRecursive(out, arguments[i]);
  }
  return out;
}

Kilka przykładów

Ustaw innerHTML i styl elementu HTML

mergeRecursive(
  document.getElementById('mydiv'),
  {style: {border: '5px solid green', color: 'red'}},
  {innerHTML: 'Hello world!'});

Scal tablice i obiekty. Zauważ, że niezdefiniowany może być użyty do zachowania wartości w lewej tablicy / obiekcie.

o = mergeRecursive({a:'a'}, [1,2,3], [undefined, null, [30,31]], {a:undefined, b:'b'});
// o = {0:1, 1:null, 2:[30,31], a:'a', b:'b'}

Każdy argument nie będący obiektem JavaScript (w tym null) zostanie zignorowany. Oprócz pierwszego argumentu odrzucane są również węzły DOM. Strzeż się, że np. Łańcuchy utworzone jak new String () są w rzeczywistości obiektami.

o = mergeRecursive({a:'a'}, 1, true, null, undefined, [1,2,3], 'bc', new String('de'));
// o = {0:'d', 1:'e', 2:3, a:'a'}

Jeśli chcesz scalić dwa obiekty w nowy (bez wpływu na jeden z dwóch), podaj {} jako pierwszy argument

var a={}, b={b:'abc'}, c={c:'cde'}, o;
o = mergeRecursive(a, b, c);
// o===a is true, o===b is false, o===c is false

Edytuj (przez ReaperSoon):

Aby także scalić tablice

function mergeRecursive(obj1, obj2) {
  if (Array.isArray(obj2)) { return obj1.concat(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 if (Array.isArray(obj2[p])) {
        obj1[p] = obj1[p].concat(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;
}


5

Powinieneś użyć wartości domyślnych lodashDeep

_.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
// → { 'user': { 'name': 'barney', 'age': 36 } }

5

W Underscore.js , aby scalić tablicę obiektów:

var arrayOfObjects = [ {a:1}, {b:2, c:3}, {d:4} ];
_(arrayOfObjects).reduce(function(memo, o) { return _(memo).extend(o); });

Powoduje to:

Object {a: 1, b: 2, c: 3, d: 4}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.