Як я можу динамічно об'єднати властивості двох об’єктів JavaScript?


2518

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

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

obj1.merge(obj2);

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

Хтось має сценарій для цього чи знає побудований спосіб зробити це? Рекурсія мені не потрібна, і мені не потрібно об’єднувати функції, просто методи на плоских об'єктах.


Варто відзначити цю відповідь на аналогічне запитання , яке показує, як об’єднати "один рівень вниз". Тобто він об'єднує значення дублюючих ключів (замість того, щоб переписати перше значення з другим значенням), але не повторюється далі. ІМХО, його хороший чистий код для цього завдання.
ToolmakerSteve

BTW, кілька перших відповідей роблять "неглибоке" злиття: якщо однаковий ключ існує і в 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

Відповіді:


2905

Стандартний метод 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-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 )


Метод для 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

69
Це робить лише неглибоку копію / злиття. Має потенціал для того, щоб розрушити багато елементів.
Джей Тейлор

45
+1 за визнання того, що деякі бідні душі змушені використовувати рамки, які
лаються

4
Це не спрацювало для мене через "Тому він призначає властивості, а не просто копіювати або визначати нові властивості. Це може зробити його непридатним для об'єднання нових властивостей у прототип, якщо джерела злиття містять гетери." ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ). Мені довелося користуватися var merged = {...obj1, ...obj2}.
морґлер

2
@ Зеєв{...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.

166
Обережно: хоч змінна "налаштування" буде змінена. jQuery не повертає новий екземпляр. Причиною цього (і для називання) є те, що .extend () був розроблений для розширення об'єктів, а не для об'єднання матеріалів разом. Якщо ви хочете новий об'єкт (наприклад, параметри за замовчуванням, які ви не хочете торкатися), ви завжди можете jQuery.extend ({}, налаштування, параметри);
вебмат

1
Прямо! Дякую купу. Як було сказано в попередньому коментарі, це назва погано. Я шукав документи jQuery назад і четверте, і не натрапив на нього.
Майк Старов

28
Зауважте, jQuery.extend також має глибокі (булі) налаштування. jQuery.extend(true,settings,override), що важливо, якщо властивість у налаштуваннях містить об'єкт, а переопределення містить лише частину цього об'єкта. Замість видалення незрівняних властивостей глибока настройка оновиться лише там, де вона існує. За замовчуванням - false.
vol7ron

19
Боже віллікери, вони справді добре зробили цю назву. Якщо ви шукаєте, як розширити об’єкт Javascript, він підходить прямо! Ви, можливо, не вдаритесь по ньому, якщо ви намагаєтеся сплавити або зшити об'єкти Javascript.
Дерек Грір

2
Не кажіть людям розглянути можливість використання бібліотечного методу, коли пара рядків JS ванілі зробить те, що вони хочуть. Був час до jQuery!
CrazyMerlin

347

Harmony ECMAScript 2015 (ES6) вказує , Object.assignякий буде робити це.

Object.assign(obj1, obj2);

Поточна підтримка веб-переглядача стає все кращою , але якщо ви розробляєте для браузерів, які не мають підтримки, ви можете використовувати поліфункцію .


35
Зауважте, що це лише неглибоке злиття
Йоахім Лос

Я думаю, тим часом 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 OP спеціально вказує на те, що рекурсія непотрібна. Тому, хоча корисно знати, що це рішення здійснює неглибоке злиття, ця відповідь є достатньою, як є. Коментар JoachimLou отримав заслужені результати та надає додаткову інформацію при необхідності. Я думаю, що різниця між глибоким та неглибоким злиттям та подібними темами виходить за межі цієї дискусії та краще підходить для інших питань ПЗ.
NanoWizard

264

Я шукав код для об'єднання властивостей об'єкта і опинився тут. Однак оскільки не було коду для рекурсивного злиття, я написав його сам. (Можливо, розширення jQuery є рекурсивним 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 як

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

11
Добре, але я б спершу зробив глибоку копію об'єктів. Таким чином, o1 також буде змінено, оскільки об'єкти передаються за посиланням.
skerit

1
Це було те, що я шукав. Не забудьте перевірити, чи є obj2.hasOwnProperty (p), перш ніж перейти до заяви про випробування. В іншому випадку ви можете закінчити злиття іншої лайки з вище в ланцюзі прототипу.
Адам

3
Дякую Маркусу. Зауважимо, що o1 також модифікований MergeRecursive, тому наприкінці o1 та o3 є однаковими. Це може бути інтуїтивніше, якщо ви нічого не повернете, тому користувач знає, що те, що вони постачають (o1), змінюється і стає результатом. Крім того, у вашому прикладі, можливо, варто додати щось у o2, що спочатку не є o1, щоб показати, що властивості o2 вставляються в o1 (хтось нижче подав альтернативний код, який копіює ваш приклад, але їх розрив, коли o2 містить властивості не в o1 - але ваші в цьому відношенні працюють).
млинець

7
Це хороша відповідь, але пара кажуть: 1) жодна логіка не повинна базуватися на винятках; у цьому випадку будь-який викинутий виняток буде спійманий з непередбачуваними результатами. 2) Я погоджуюся з коментарем @ pancake про те, що нічого не повертати, оскільки оригінальний об’єкт модифікований. Ось приклад jsFiddle без логіки винятку: jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery
Jeff Lowery

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);
з’єднайтесь

8
Є ще один, який може бути актуальним залежно від того, що ви намагаєтеся досягти: _.defaults(object, *defaults)"Заповніть нульові та невизначені властивості в об'єкті зі значеннями об'єктів за замовчуванням та поверніть об'єкт."
conny

Багато людей коментують мутацію об’єкта призначення, але замість того, щоб використовувати метод створення нового, загальним шаблоном (наприклад, у доджо) є те, щоб сказати dojo.mixin (dojo.clone (myobj), {newpropertys: "додати до myobj "})
Колін D

7
Підкреслення може приймати довільну кількість об'єктів, тому ви можете уникнути мутації оригінального об'єкта, виконуючи це var mergedObj = _.extend({}, obj1, obj2).
lobati

84

Подібно до jQuery extension (), ви маєте таку ж функцію в 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 Я думаю, що це буде варіантом, якщо ви не хочете додати lib лише для цього fn.
juanmf

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
хороша відповідь, але я думаю, якщо властивість присутня в першому і в другому, і ви намагаєтеся створити новий об'єкт, об'єднавши перше і друге, то унікальні, присутні в першому об'єкті, будуть втрачені
Нападники

2
Але чи не очікувана поведінка? Метою цієї функції є переосмислення значень у порядку аргументів. Наступний переможе струм. Як можна зберегти унікальні цінності? З мого прикладу, чого ти очікуєш merge({}, t1, { key1: 1 })?
Емре Еркан

Моє сподівання полягає в об'єднанні лише нетипізованих значень.
AGamePlayer

не вдається, якщо очікуваний результат такий: gist.github.com/ceremcem/a54f90923c6e6ec42c7daf24cf2768bd
ceremcem

Це працювало для мене в суворому режимі для мого проекту node.js. Поки що це найкраща відповідь у цій публікації.
klewis

48

Ви можете використовувати властивості розповсюдження об'єктів - як правило, третя стадія пропозиції ECMAScript.

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

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


Підтримка браузера?
Alph.Dev

1
@ Alph.Dev Ти знаєш caniuse.com?
коннексо

Я не можу змусити 3-кратний синтаксис працювати в node.js. вузол скаржиться на це.
klewis

41

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


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; тощо], не зовсім старі. Вони є лише більш ранніми версіями gen.5 UA.
Бекім Бакай

Дякуємо за уточнення, що Objects.assignповертає об'єднаний об'єкт, на який не відповідають інші. Це більш функціональний стиль програмування.
Шрідхар Сарнобат

36

Наступні два - мабуть, хороша відправна точка. lodash також має функцію налаштування для тих особливих потреб!

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


4
Зверніть увагу, що вищезазначені методи мутують вихідний об'єкт.
Wtower

Метод lodash працював для мене, оскільки я хотів об'єднати два об'єкти і зберегти властивості, вкладені більше ніж на один або два рівні глибоко (а не просто замінити / витерти їх).
SamBrick

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

3
Допомогли мені дуже, підкреслення js розширення насправді перезаписало замість злиття
David Cumps

1
Варто зауважити, що масив typeof та typeof null повернуть 'object' та порушують це.
Салакар

@ U01jmg3 побачити моя відповідь тут: stackoverflow.com/questions/27936772 / ... - функція IsObject
Salakar


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

Порівняно з іншими, які я перевірив на цій сторінці, ця функція справді рекурсивна (робить глибоке злиття) і імітує те, що jQuery.extend () справді добре. Однак було б краще змінити перший об'єкт / аргумент, щоб користувач міг вирішити, чи хоче він передати порожній об’єкт {}як перший параметр чи змусити функцію змінити вихідний об'єкт. Тому, якщо ви перейдете dst = {}на dst = arguments[0]нього, буде змінено перший об'єкт, який ви передасте об'єднаному об'єкту
Джасдіп Халса

3
Тепер він перестав працювати в хромі. Я думаю, що погано використовувати таку конструкцію toString.call(src) == '[object Object]'для перевірки, є об’єкт чи ні. typeof srcнабагато краще. Більше того, це перетворює масиви в об’єкт (У leas у мене така поведінка на останньому хромі).
m03geek

Мені потрібно було це зробити в жадібному циклі, я відзначив цю функцію VS тією, якою я користувався досі _.merge (об’єкт, [джерела]) від 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_/

Зауважте, що в цьому прикладі "} {" не повинно виникати в рядку!


4
var so1 = JSON.stringify(obj1); var so2 = JSON.stringify(obj1); objMerge = so1.substr(0, so1.length-1)+","+so2.substr(1); console.info(objMerge);
Кваміс

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, яка є першим браузером, який впровадив свій стандарт. Це просто змушує мене замислитися "чому о, чому ти повинен був сказати таку дурну штуку і піти з цього"?
Бекім Бакай

Я вірю, що функції в об'єкті тут будуть руйнуватися, оскільки не можна використовувати строфіфіковані, але чи можна уточнити інші недоліки?
yeahdixon

18

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

Я б схильний використовувати або вдосконалювати це замість копіювання-вставлення коду з відповідей.


17

Найкращий спосіб зробити це - додати належне властивість, яке не перелічується, використовуючи Object.defineProperty.

Таким чином, ви все одно зможете переглядати властивості своїх об'єктів, не маючи новоствореного "розширення", яке ви отримали, якби створили властивість за допомогою Object.prototype.extend.

Сподіваємось, це допомагає:

Object.defineProperty (Object.prototype, "розширити", {
    перераховується: помилково,
    значення: функція (від) {
        var props = Object.getOwnPropertyNames (від);
        var dest = це;
        props.forEach (функція (ім'я) {
            if (ім'я в dest) {
                var призначення = Object.getOwnPropertyDescriptor (від, ім'я);
                Object.defineProperty (dest, ім'я, призначення);
            }
        });
        повернути це;
    }
});

Після того, як ви працюєте, ви можете:

var obj = {
    назва: 'stack',
    закінчення: 'переповнення'
}
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


2
obj1 буде містити всі значення, а не obj2. Ви розширюєте перший об’єкт. jQuery.extend( target [, object1 ] [, objectN ] )
ruuter

5
Це питання не стосується jQuery. JavaScript! = JQuery
Хорхе Рів

1
Я переглядав JavaScript, але це простіший підхід
Ехсан

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. Перевірте цей детальний огляд бібліотеки прототипів 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 :

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'

10

Я продовжив метод Девіда Коллієра:

  • Додана можливість об'єднання декількох об'єктів
  • Підтримує глибокі предмети
  • параметр переопределення (виявляється, якщо останній параметр є булевим)

Якщо переопределення помилкове, жодне властивість не буде перекрито, але нові властивості будуть додані.

Використання: 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

Нічого ... це перша публікація 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'}}

Ця відповідь - це лише крапля в океані ...


Найкоротша відповідь, яка чудово працює! Це заслуговує більшої кількості оновлень!
Jens Törnell

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

На основі відповіді Маркуса " і 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;
}

Деякі приклади

Встановіть InternalHTML та стиль HTML-елемента

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

Об'єднати масиви та об'єкти. Зауважте, що невизначені можуть використовуватися для збереження значень у лівому масиві / об'єкті.

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 (включаючи null), буде ігнорований. За винятком першого аргументу, також відкидаються також вузли DOM. Остерігайтеся, тобто струни, створені як новий 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;
}


5

Слід використовувати типові значення lodashDeep

_.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}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.