Как динамически объединить свойства двух объектов JavaScript?

2522

Мне нужно объединить два (очень простых) объекта JavaScript во время выполнения. Например, я хотел бы:

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

obj1.merge(obj2);

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

У кого-нибудь есть сценарий для этого или знаете встроенный способ сделать это? Мне не нужна рекурсия, и мне не нужно объединять функции, только методы для плоских объектов.

JC Grubbs
источник
Стоит отметить этот ответ на аналогичный вопрос , который показывает, как объединить «один уровень вниз». Таким образом, он объединяет значения дубликатов ключей (вместо того, чтобы перезаписывать первое значение вторым значением), но не обрабатывает дальше, чем это. ИМХО, это хороший чистый код для этой задачи.
ToolmakerSteve
Кстати, первые несколько ответов выполняют «поверхностное» слияние: если один и тот же ключ существует в obj1 и obj2, значение в obj2 сохраняется, значение в obj1 удаляется. Например, если бы пример вопроса был var obj2 = { animal: 'dog', food: 'bone' };, слияние было бы { food: 'bone', car: 'ford', animal: 'dog' }. Если вы работаете с «вложенными данными» и хотите «глубокое слияние», ищите ответы, в которых упоминается «глубокое слияние» или «рекурсия». Если у вас есть значения, которые есть arrays, то используйте опцию «arrayMerge» в github «TehShrike / deepmerge», как упомянуто здесь .
ToolmakerSteve
Пожалуйста, перейдите по ссылке ниже stackoverflow.com/questions/171251/… Оператор распространения, который является новой функцией в версии
ES6

Ответы:

2911

ECMAScript 2018 Стандартный метод

Вы бы использовали объект распространения :

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

mergedтеперь союз obj1и obj2. Свойства в obj2перезапишут те в 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};

Вот также документация MDN для этого синтаксиса. Если вы используете babel, вам понадобится плагин babel-plugin-transform-object-rest-spread, чтобы он работал.

ECMAScript 2015 (ES6) Стандартный метод

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

(см. MDN JavaScript Reference )


Метод для ES5 и ранее

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

Обратите внимание , что это будет просто добавить все атрибуты obj2для obj1которых не может быть то , что вы хотите , если вы все еще хотите использовать неизмененный obj1.

Если вы используете фреймворк, охватывающий все ваши прототипы, вам придется потрудиться с подобными проверками hasOwnProperty, но этот код будет работать в 99% случаев.

Пример функции:

/**
 * 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
Это не работает, если объекты имеют одинаковые атрибуты имени, и вы также захотите объединить атрибуты.
Xiè Jìléi 24.10.10
69
Это делает только поверхностное копирование / слияние. Имеет потенциал, чтобы заглушить много элементов.
Джей Тейлор
45
+1 за признание того, что некоторые бедные души вынуждены использовать фреймворки, которые портят все их прототипы ...
thejonwithnoh
4
Это не сработало для меня из-за того, что «поэтому он назначает свойства вместо простого копирования или определения новых свойств. Это может сделать его непригодным для объединения новых свойств в прототип, если источники слияния содержат геттеры». ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ). Я должен был использовать var merged = {...obj1, ...obj2}.
торговец
2
@ Ze'ev{...obj1, ...{animal: 'dog'}}
backslash112
1191

У jQuery также есть утилита для этого: http://api.jquery.com/jQuery.extend/ .

Взято из документации по 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" }

Приведенный выше код будет мутировать существующий объект с именем settings.


Если вы хотите создать новый объект без изменения любого аргумента, используйте это:

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.
Avdi
источник
166
Осторожно: переменная «настройки» будет изменена. JQuery не возвращает новый экземпляр. Причина этого (и именования) заключается в том, что .extend () был разработан для расширения объектов, а не для того, чтобы разбирать вещи вместе. Если вы хотите новый объект (например, настройки - это настройки по умолчанию, к которым вы не хотите прикасаться), вы всегда можете jQuery.extend ({}, настройки, опции);
вебмат
1
Право на! Огромное спасибо. Как говорилось в предыдущем комментарии, он плохо назван. Я искал документы JQuery назад и четвертый и не столкнулся с ним.
Майк Старов
28
Имейте в виду, jQuery.extend также имеет глубокий (логический) параметр. jQuery.extend(true,settings,override), что важно, если свойство в настройках содержит объект, а переопределение имеет только часть этого объекта. Вместо удаления несогласованных свойств глубокий параметр будет обновляться только там, где он существует. По умолчанию установлено значение false.
vol7ron
19
Ну и дела, они действительно сделали хорошую работу, назвав это. Если вы ищете, как расширить объект Javascript, он подходит! Вы можете не столкнуться с этим, хотя, если вы пытаетесь соединить или соединить объекты Javascript.
Дерек Грир
2
Не говорите людям об использовании библиотечного метода, когда пара строк vanilla JS будет делать то, что они хотят. Было время, прежде чем JQuery!
CrazyMerlin
348

Harmony ECMAScript 2015 (ES6) указывает , Object.assignкоторый будет делать это.

Object.assign(obj1, obj2);

Текущая поддержка браузеров становится лучше , но если вы разрабатываете для браузеров, которые не поддерживают, вы можете использовать полифилл .

NanoWizard
источник
35
Обратите внимание, что это только поверхностное слияние
Joachim Lous
Я думаю, что тем временем Object.assignимеет довольно приличное освещение: kangax.github.io/compat-table/es6/…
LazerBass
13
Теперь это должен быть правильный ответ. В настоящее время люди компилируют свой код для совместимости с редким браузером (IE11), который не поддерживает подобные вещи. Примечание: если вы не хотите добавлять obj2 в obj1, вы можете вернуть новый объект, используяObject.assign({}, obj1, obj2)
Duvrai
6
@Duvrai Согласно отчетам, которые я видел, IE11 определенно не редкость и составляет около 18% доли рынка по состоянию на июль 2016 года.
NanoWizard
3
@Kermani ОП особо отмечает, что рекурсия не нужна. Поэтому, хотя полезно знать, что это решение выполняет поверхностное объединение, этого ответа достаточно, как есть. Комментарий JoachimLou получил заслуженные отклики и предоставляет дополнительную информацию при необходимости. Я думаю, что разница между глубоким и мелким слиянием и схожими темами выходит за рамки этого обсуждения и лучше подходит для других вопросов SO.
NanoWizard
264

Я гуглил код для слияния свойств объекта и оказался здесь. Однако, поскольку не было никакого кода для рекурсивного слияния, я написал его сам. (Может быть, JQuery Extench является рекурсивным BTW?) В любом случае, надеюсь, кто-то еще найдет это полезным.

(Сейчас код не использует Object.prototype:)

Код

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

Пример

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

Создает объект o3 like

o3 = {  a : 10,
        b : 2,
        c : {
          ca : 10,
          cb : 20,
          cc : { 
            cca : 101,
            ccb : 202 } } };
Markus
источник
11
Хорошо, но я бы сначала сделал глубокую копию объектов. Таким образом, o1 также будет изменен, так как объекты передаются по ссылке.
скерит
1
Это было то, что я искал. Убедитесь, что вы проверили obj2.hasOwnProperty (p), прежде чем переходить к оператору try catch. В противном случае вы можете получить еще одну хрень, влившуюся в цепь прототипа.
Адам
3
Спасибо Маркус. Обратите внимание, что o1 также модифицируется MergeRecursive, поэтому в конце o1 и o3 идентичны. Это может быть более интуитивно понятно, если вы ничего не возвращаете, поэтому пользователь знает, что то, что он предоставляет (o1), изменяется и становится результатом. Кроме того, в вашем примере, возможно, стоит добавить что-то в o2, которое изначально не было в o1, чтобы показать, что свойства o2 вставляются в o1 (кто-то еще ниже представил альтернативный код, копирующий ваш пример, но их разрывает, когда o2 содержит свойства, не в о1 - но ваш работает в этом отношении).
блин
7
Это хороший ответ, но пара утешает: 1) никакая логика не должна основываться на исключениях; в этом случае любое выброшенное исключение будет обнаружено с непредсказуемым результатом. 2) Я согласен с комментарием @ блин о том, что ничего не возвращается, так как исходный объект изменен. Вот пример jsFiddle без логики исключений: jsfiddle.net/jlowery2663/z8at6knn/4 - Джефф Лоури
Джефф Лоури
3
Кроме того, obj2 [p] .constructor == Массив необходим в любом случае, когда существует массив объектов.
Композитор
175

Обратите внимание , что underscore.js«S extend-метод делает это в однострочнике:

_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}
индастриал
источник
36
Этот пример хорош, поскольку вы имеете дело с анонимными объектами, но если это не так, то здесь применяется комментарий webmat в ответе jQuery с предупреждением о мутациях, так как подчеркивание также изменяет целевой объект . Как и в ответе jQuery, чтобы сделать это без мутаций, просто слиться с пустым объектом:_({}).extend(obj1, obj2);
Abe Voelker
8
Есть еще один, который может иметь значение в зависимости от того, чего вы пытаетесь достичь: _.defaults(object, *defaults)«Заполните нулевые и неопределенные свойства в объекте значениями из объектов по умолчанию и верните объект».
Конни
Многие люди прокомментировали изменение объекта назначения, но вместо того, чтобы метод создавал новый, общий шаблон (например, в dojo) состоит в том, чтобы сказать dojo.mixin (dojo.clone (myobj), {newpropertys: "добавить to myobj "})
Колин Д.
7
Подчеркивание может принимать произвольное количество объектов, так что вы можете избежать мутации исходного объекта, выполнив var mergedObj = _.extend({}, obj1, obj2).
Лобати
84

Подобно jQuery extend (), у вас есть такая же функция в 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);
AndreasE
источник
1
@naXa Я думаю, что будет вариант, если вы не хотите добавлять библиотеку только для этого fn.
июня
67

Мне нужно объединить объекты сегодня, и этот вопрос (и ответы) мне очень помог. Я попробовал некоторые ответы, но ни один из них не соответствовал моим потребностям, поэтому я соединил некоторые ответы, добавил что-то сам и предложил новую функцию слияния. Вот:

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

Некоторые примеры использования:

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
хороший ответ, но я думаю, что если свойство присутствует в первом и втором, и вы пытаетесь создать новый объект путем слияния первого и второго, то уникальные, присутствующие в первом объекте, будут потеряны
Strikers
2
Но разве это не ожидаемое поведение? Цель этой функции - переопределить значения в порядке аргументов. Следующий переопределит текущий. Как вы можете сохранить уникальные ценности? Что вы ожидаете от моего примера merge({}, t1, { key1: 1 })?
Эмре Эркан
Я ожидаю объединения только не типизированных значений.
AGamePlayer
не удается, если ожидаемый результат выглядит следующим образом: gist.github.com/ceremcem/a54f90923c6e6ec42c7daf24cf2768bd
ceremcem
Это работало для меня в строгом режиме для моего проекта node.js. Пока что это лучший ответ на этот пост.
Klewis
48

Вы можете использовать свойства распространения объекта - в настоящее время предложение ECMAScript этапа 3.

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

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

Jaime Asm
источник
Поддержка браузера?
Alph.Dev
1
@ Alph.Dev Вы знаете, caniuse.com?
Коннексо
Я не могу заставить синтаксис с 3 точками работать в node.js. узел жалуется на это.
Klewis
41

Данные решения должны быть изменены , чтобы проверить source.hasOwnProperty(property)в for..inпетлях до назначения - в противном случае, вы в конечном итоге копирование свойств всей цепи прототипов, которая редко желаемому ...

Christoph
источник
40

Объединить свойства N объектов в одну строку кода

Object.assignМетод является частью 2015 (ES6) стандарта ECMAScript и делает именно то , что вам нужно. ( IEне поддерживается)

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

Метод Object.assign () используется для копирования значений всех перечисляемых собственных свойств из одного или нескольких исходных объектов в целевой объект.

Читать далее...

Polyfill для поддержки старых браузеров:

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;
    }
  });
}
Евгений Тюрин
источник
Что делает «использовать строгий» в старых браузерах или почему оно вообще есть. браузеры, которые поддерживают объект [defineProperty; ключи; getOwnPropertyDescriptor; и т. д.], не совсем старые. Это просто более ранние версии UA 5 поколения.
Беким Бакай
Спасибо за разъяснение, что Objects.assignвозвращает объединенный объект, чего нет в других ответах. Это более функциональный стиль программирования.
Шридхар Сарнобат
36

Следующие два, вероятно, являются хорошей отправной точкой. У lodash также есть функция настройки для этих особых нужд!

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

приложений
источник
4
Обратите внимание, что вышеуказанные методы видоизменяют исходный объект.
Wtower
Метод lodash работал для меня, так как я хотел объединить два объекта и сохранить свойства, вложенные глубиной более одного или двух уровней (а не просто заменить / стереть их).
СамБрик
правда @Wtower. Просто была ошибка из-за этого. Лучше всего убедиться, что он всегда начинается как _.merge ({}, ...
Мартин Кремер
29

Кстати, все, что вы делаете, это перезаписывает свойства, а не объединяет ...

Вот как действительно объединилась область объектов JavaScript: только ключи в toобъекте, которые сами не являются объектами, будут перезаписаны from. Все остальное будет действительно объединено . Конечно, вы можете изменить это поведение, чтобы не перезаписывать все, что существует, например, если to[n] is undefined, и т. Д ...:

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

Применение:

var merged = realMerge(obj1, obj2);
Andreas Linden
источник
3
Мне очень
помогло
1
Стоит отметить, что typeof array и typeof null вернут 'object' и сломают это.
Салакар
@ u01jmg3 смотрите мой ответ здесь: stackoverflow.com/questions/27936772/… - функция
isObject
Это ломается, если вы используете строгий режим для проектов node.js
klewis
28

Вот мой удар, который

  1. Поддерживает глубокое слияние
  2. Не мутирует аргументы
  3. Принимает любое количество аргументов
  4. Не расширяет объект-прототип
  5. Не зависит от другой библиотеки ( jQuery , MooTools , Underscore.js и т. Д.)
  6. Включает проверку на hasOwnProperty
  7. Короткий :)

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

Пример:

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
    };
*/
Paul Spaulding
источник
По сравнению с другими, которые я тестировал на этой странице, эта функция действительно рекурсивна (выполняет глубокое слияние) и имитирует то, что jQuery.extend () делает действительно хорошо. Однако для него было бы лучше изменить первый объект / аргумент, чтобы пользователь мог решить, хотят ли они передать пустой объект{} в качестве первого параметра или сделать так, чтобы функция изменила исходный объект. Поэтому , если вы измените dst = {}к dst = arguments[0]ней изменится первый объект , который пройдет в объединенном к объекту
Jasdeep Халса
3
Теперь он перестал работать в Chrome. Я думаю, что это плохая идея использовать такую ​​конструкцию toString.call(src) == '[object Object]'для проверки, есть ли объект или нет. typeof srcнамного лучше. Более того, он превращает массивы в объект (по крайней мере, у меня такое поведение на последнем Chrome).
m03geek
Мне нужно было сделать это внутри жадного цикла, я сравнил эту функцию с той, которую я использовал до сих пор _.merge (object, [sources]) из lodash (своего рода подчеркивание lib): ваша функция почти в два раза быстрее Спасибо друг.
Арно Бушо
20

Object.assign ()

ECMAScript 2015 (ES6)

Это новая технология, часть стандарта ECMAScript 2015 (ES6). Спецификация этой технологии была доработана, но проверьте таблицу совместимости для использования и состояния реализации в различных браузерах.

Метод Object.assign () используется для копирования значений всех перечисляемых собственных свойств из одного или нескольких исходных объектов в целевой объект. Он вернет целевой объект.

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

Для не слишком сложных объектов вы можете использовать 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_/

Помните, что в этом примере "} {" не должно встречаться в строке!

Algy
источник
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, '{')); }
Алекс Ивасюв
или как однострочник:objMerge = JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)).replace(/\}\{/, ", "));
Раввин Шуки Гур
1
@Charles, извините, но как «этот метод очень опасен», но также и забавен - это максимально безопасно. Во-первых, он использует JSON для тяжелой работы по чтению содержимого объекта, во-вторых, он использует объект JSON, который, безусловно, самый безопасный. машина оптимизирована для повторного анализа JSO из строкового литерала объекта, совместимого с JSON. И также обратно совместим с IE8, который является первым браузером, который реализует свой стандарт. Это просто заставляет меня задуматься: «Почему ты должен был сказать такую ​​глупость и сойти с рук»?
Беким Бакай
Я полагаю, что функции в объекте будут разрушены, так как stringify не может быть использован, но можем ли мы прояснить другие недостатки?
агадиксон
18

Там есть библиотека называется deepmergeна GitHub : Это , кажется, получает некоторую тягу. Это автономная версия, доступная через менеджеры пакетов npm и bower.

Я был бы склонен использовать или улучшить это вместо того, чтобы копировать код из ответов.

Peter Mortensen
источник
17

Лучший способ сделать это - добавить правильное свойство, которое нельзя перечислить, используя Object.defineProperty.

Таким образом, вы по-прежнему сможете перебирать свойства ваших объектов, не имея вновь созданного «расширения», которое вы получите, если бы вы создали свойство с помощью Object.prototype.extend.

Надеюсь, это поможет:

Object.defineProperty (Object.prototype, "extend", {
    перечислимый: ложь,
    значение: функция (с) {
        var props = Object.getOwnPropertyNames (from);
        var dest = this;
        props.forEach (function (name) {
            if (имя в dest) {
                var destination = Object.getOwnPropertyDescriptor (from, name);
                Object.defineProperty (dest, name, destination);
            }
        });
        верни это;
    }
});

Как только у вас это заработает, вы можете сделать:

var obj = {
    имя: «стек»,
    отделка: «переполнение»
}
замена var = {
    название: «сток»
};

obj.extend (замена);

Я только что написал пост в блоге об этом здесь: http://onemoredigit.com/post/1527191998/extending-objects-in-node-js

Дэвид Коллиер
источник
Подождите секунду --- код не работает! :( вставил его в jsperf для сравнения с другими алгоритмами слияния, и код завершился ошибкой - расширение функции не существует
Jens Roland
16

Вы можете просто использовать JQuery extend

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

jQuery.extend(obj1, obj2);

Теперь obj1содержит все значения obj1иobj2

Dinusha
источник
2
obj1 будет содержать все значения, а не obj2. Вы расширяете первый объект. jQuery.extend( target [, object1 ] [, objectN ] )
рутер
5
Этот вопрос не о jQuery. JavaScript! = JQuery
Хорхе Рив
1
Я погуглил для JavaScript, но это более простой подход
Ehsan
13

Прототип имеет это:

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

obj1.extend(obj2) будет делать то, что вы хотите.

эфимант
источник
6
Вы имели в виду Object.prototype.extend? В любом случае, не рекомендуется расширять Object.prototype. -1.
SolutionYogi
Если подумать, то, вероятно, должно быть, Object.prototypeа не Object... Хорошая идея или нет, это то, что prototype.jsделает фреймворк, и поэтому, если вы используете его или другой фреймворк, который зависит от него, Object.prototypeон уже будет расширен extend.
Эфимент
2
Я не хочу говорить вам, но говорю, что это нормально, потому что prototype.js - плохой аргумент. Prototype.js популярен, но это не значит, что они являются примером для подражания в том, как делать JavaScript. Проверьте этот подробный обзор библиотеки Prototype от JS pro. dhtmlkitchen.com/?category=/JavaScript/&date=2008/06/17/…
SolutionYogi
7
Кого волнует, правильно это или неправильно? Если это работает, то это работает. Ожидание идеального решения - это здорово, за исключением попыток сохранить контракт, привлечь новых клиентов или уложиться в сроки. Дайте здесь любого, кто увлечен программированием свободных денег, и они в конце концов сделают все «правильно».
Дэвид
1
Где вы все нашли, ребята Object.prototype? Object.extendне будет мешать вашим объектам, он просто присоединяет функцию к Objectобъекту. И obj1.extend(obj2)это не правильный способ срабатывания функции, используй Object.extend(obj1, obj2)insted
artnikpro
13

** Объединение объектов простое использование Object.assignили ...оператор распространения **

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

Объекты объединяются справа налево, это означает, что объекты, которые имеют идентичные свойства с объектами справа, будут переопределены.

В этом примере obj2.carпереопределяетobj1.car

Легенды
источник
12

Просто если кто-то использует библиотеку Google Closure :

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

Аналогичная вспомогательная функция существует для массива :

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'
orian
источник
10

Я расширил метод Дэвида Коллие:

  • Добавлена ​​возможность объединения нескольких объектов
  • Поддерживает глубокие объекты
  • параметр переопределения (это определяется, если последний параметр является логическим)

Если override имеет значение false, никакое свойство не будет переопределено, но будут добавлены новые свойства.

Использование: obj.merge (сливается ... [, переопределить]);

Вот мой код:

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

Примеры и тестовые случаи:

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

Мой метод равно можно найти здесь: Сравнение объектов в JavaScript

оборота госси
источник
9

В Ext JS 4 это можно сделать следующим образом:

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

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

Смотрите слияние (объект): объект .

отметка
источник
1
Остерегайтесь ошибки EXTJS-9173 в EXT.Object.merge, поскольку +2 года до сих пор не устранены
Крис
9

Ого ... это первый пост StackOverflow, который я видел на нескольких страницах. Извиняюсь за добавление еще одного "ответа"

Этот метод предназначен для ES5 и более ранних версий - есть много других ответов на ES6.

Я не видел никакого "глубокого" слияния объекта с использованием argumentsсвойства. Вот мой ответ - компактный и рекурсивный , позволяющий передавать неограниченные аргументы объекта:

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

Закомментированная часть является необязательной. Она просто пропускает передаваемые аргументы, которые не являются объектами (предотвращая ошибки).

Пример:

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

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

Этот ответ сейчас капля в море ...

логан
источник
Самый короткий ответ, который прекрасно работает! Это заслуживает большего количества голосов!
Дженс Торнелл
8
var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog' }

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

Использование jQuery.extend () - Ссылка

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

Использование _.merge () - Ссылка

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

Использование _.extend () - Ссылка

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

Использование Object.assign () ECMAScript 2015 (ES6) - Ссылка

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

Выход всего

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

Основываясь на ответах Markus и vsync , это расширенная версия. Функция принимает любое количество аргументов. Он может использоваться для установки свойств на узлах DOM и делает глубокие копии значений. Тем не менее, первый аргумент дается ссылкой.

Чтобы обнаружить узел DOM, используется функция isDOMNode () (см. Вопрос переполнения стека JavaScript isDOM - Как проверить, является ли объект JavaScript объектом DOM? )

Он был протестирован в Opera 11, Firefox 6, Internet Explorer 8 и Google Chrome 16.

Код

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

Некоторые примеры

Установить innerHTML и стиль элемента HTML

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

Слияние массивов и объектов. Обратите внимание, что undefined может использоваться для сохранения значений в левом массиве / объекте.

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

Любой аргумент, не являющийся объектом JavaScript (включая ноль), будет игнорироваться. За исключением первого аргумента, также DOM-узлы отбрасываются. Помните, что строки, созданные как new String (), на самом деле являются объектами.

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

Если вы хотите объединить два объекта в новый (не затрагивая ни один из двух), укажите {} в качестве первого аргумента

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

Изменить (от ReaperSoon):

Также объединить массивы

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;
}
Snoozer Man
источник
5

Вы должны использовать lodash по умолчаниюDeep

_.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
// → { 'user': { 'name': 'barney', 'age': 36 } }
Рафаель
источник
5

С Underscore.js , чтобы объединить массив объектов, сделайте:

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

Это приводит к:

Object {a: 1, b: 2, c: 3, d: 4}
devmao
источник