Làm cách nào tôi có thể hợp nhất các thuộc tính của hai đối tượng JavaScript một cách linh hoạt?


2519

Tôi cần có khả năng hợp nhất hai đối tượng JavaScript (rất đơn giản) khi chạy. Ví dụ tôi muốn:

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

obj1.merge(obj2);

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

Có ai có một kịch bản cho điều này hoặc biết một cách xây dựng để làm điều này? Tôi không cần đệ quy và tôi không cần hợp nhất các hàm, chỉ cần các phương thức trên các đối tượng phẳng.


Đáng chú ý câu trả lời này cho một câu hỏi tương tự , trong đó cho thấy làm thế nào để hợp nhất "một cấp xuống". Nghĩa là, nó hợp nhất các giá trị của các khóa trùng lặp (thay vì ghi đè giá trị thứ nhất với giá trị thứ hai), nhưng không tái diễn nhiều hơn thế. IMHO, mã sạch tốt của nó cho nhiệm vụ đó.
ToolmakerSteve

BTW, một vài câu trả lời hàng đầu thực hiện hợp nhất "nông": nếu cùng một khóa tồn tại trong cả obj1 và obj2, giá trị trong obj2 được giữ, giá trị trong obj1 bị loại bỏ. Ví dụ: nếu ví dụ của câu hỏi đã có var obj2 = { animal: 'dog', food: 'bone' };, sự hợp nhất sẽ là { food: 'bone', car: 'ford', animal: 'dog' }. Nếu bạn đang làm việc với "dữ liệu lồng nhau" và muốn "hợp nhất sâu", thì hãy tìm câu trả lời có đề cập đến "hợp nhất sâu" hoặc "đệ quy". Nếu bạn có các giá trị đó arrays, thì hãy sử dụng tùy chọn "ArrayMerge" của github "TehShrike / deepmerge", như được đề cập ở đây .
ToolmakerSteve

Vui lòng theo liên kết dưới đây stackoverflow.com/questions/171251/ Từ Toán tử trải rộng là tính năng mới trong phiên bản
ES6

Câu trả lời:


2907

Phương pháp tiêu chuẩn ECMAScript 2018

Bạn sẽ sử dụng đối tượng lây lan :

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

mergedbây giờ là liên minh của obj1obj2. Thuộc tính trong obj2sẽ ghi đè lên những người trong 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};

Đây cũng là tài liệu MDN cho cú pháp này. Nếu bạn đang sử dụng babel, bạn sẽ cần plugin babel-plugin-Transform-object-rest-lây lan để nó hoạt động.

Phương pháp tiêu chuẩn 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);

(xem Tài liệu tham khảo JavaScript MDN )


Phương pháp cho ES5 và trước đó

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

Lưu ý rằng điều này sẽ chỉ cần thêm tất cả các thuộc tính của obj2để obj1mà có thể không được những gì bạn muốn nếu bạn vẫn muốn sử dụng chưa sửa đổi obj1.

Nếu bạn đang sử dụng một khung công tác phù hợp với tất cả các nguyên mẫu của mình thì bạn phải có được fancier với các kiểm tra như thế hasOwnProperty, nhưng mã đó sẽ hoạt động cho 99% trường hợp.

Hàm ví dụ:

/**
 * 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
Điều này không hoạt động nếu các đối tượng có thuộc tính tên giống nhau và bạn cũng muốn hợp nhất các thuộc tính.
Xiè Jìléi

69
Điều này chỉ làm một bản sao / hợp nhất nông. Có khả năng đóng băng rất nhiều yếu tố.
Jay Taylor

45
+1 để thừa nhận rằng một số linh hồn đáng thương buộc phải sử dụng các khuôn khổ tào lao trên khắp các nguyên mẫu của họ ...
thejonwithnoh

4
Điều này không hiệu quả với tôi vì "Do đó, nó gán các thuộc tính thay vì chỉ sao chép hoặc xác định các thuộc tính mới. Điều này có thể khiến nó không phù hợp để hợp nhất các thuộc tính mới vào một nguyên mẫu nếu các nguồn hợp nhất có chứa getters." ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/iêu ). Tôi đã phải sử dụng var merged = {...obj1, ...obj2}.
morgler

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

1191

jQuery cũng có một tiện ích cho việc này: http://api.jquery.com/jQuery.extend/ .

Lấy từ tài liệu 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" }

Đoạn mã trên sẽ biến đổi đối tượng hiện có tên settings.


Nếu bạn muốn tạo một đối tượng mới mà không sửa đổi một trong hai đối số, hãy sử dụng:

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
Tuy nhiên, cẩn thận: biến "cài đặt" sẽ được sửa đổi. jQuery không trả về một thể hiện mới. Lý do cho điều này (và cho việc đặt tên) là .extend () được phát triển để mở rộng các đối tượng, thay vì để trộn các thứ lại với nhau. Nếu bạn muốn một đối tượng mới (ví dụ: cài đặt là mặc định bạn không muốn chạm vào), bạn luôn có thể jQuery.extend ({}, cài đặt, tùy chọn);
webmat

1
Đúng rồi! Cảm ơn nhiều. Như bình luận trước đó đã nêu nó được đặt tên kém. tôi đã tìm kiếm các tài liệu jQuery trở lại và thứ tư và không chạy vào nó.
Mike Starov

28
Nhắc bạn, jQuery.extend cũng có cài đặt sâu (boolean). jQuery.extend(true,settings,override), điều này rất quan trọng nếu một thuộc tính trong cài đặt giữ một đối tượng và ghi đè chỉ có một phần của đối tượng đó. Thay vì loại bỏ các thuộc tính chưa từng có, cài đặt sâu sẽ chỉ cập nhật ở nơi nó tồn tại. Mặc định này sai.
vol7ron

19
Gee willikers, họ thực sự đã làm rất tốt khi đặt tên này. Nếu bạn tìm kiếm cách mở rộng một đối tượng Javascript, nó sẽ xuất hiện ngay! Bạn có thể không đánh vào nó mặc dù nếu bạn đang cố gắng hợp nhất hoặc ghép các đối tượng Javascript lại với nhau.
Derek Greer

2
Đừng bảo mọi người cân nhắc sử dụng phương thức thư viện khi một vài dòng vanilla JS sẽ làm những gì họ muốn. Có một thời gian trước jQuery!
CrazyMerlin

347

Các Harmony ECMAScript 2015 (ES6) quy định cụ thể Object.assignmà sẽ làm điều này.

Object.assign(obj1, obj2);

Hỗ trợ trình duyệt hiện tại đang trở nên tốt hơn , nhưng nếu bạn đang phát triển cho các trình duyệt không có hỗ trợ, bạn có thể sử dụng một polyfill .


35
Lưu ý rằng đây chỉ là một sự hợp nhất nông cạn
Joachim Lous

Tôi nghĩ trong khi đó Object.assigncó phạm vi bảo hiểm khá tốt: kangax.github.io/compat-table/es6/ mẹo
LazerBass

13
Điều này bây giờ nên là câu trả lời chính xác. Ngày nay mọi người biên dịch mã của họ để tương thích với trình duyệt hiếm (IE11) không hỗ trợ loại điều này. Lưu ý bên lề: nếu bạn không muốn thêm obj2 vào obj1, bạn có thể trả về một đối tượng mới bằng cách sử dụngObject.assign({}, obj1, obj2)
Duvrai

6
@Duvrai Theo báo cáo tôi đã thấy, IE11 chắc chắn không phải là hiếm với khoảng 18% thị phần tính đến tháng 7 năm 2016.
NanoWizard

3
@Kermani OP đặc biệt chỉ ra rằng đệ quy là không cần thiết. Do đó, mặc dù rất hữu ích khi biết rằng giải pháp này thực hiện hợp nhất nông, nhưng câu trả lời này là đủ. Nhận xét của JoachimLou đã nhận được những lời khen ngợi xứng đáng và cung cấp thêm thông tin khi cần thiết. Tôi nghĩ rằng sự khác biệt giữa sáp nhập sâu và nông và các chủ đề tương tự nằm ngoài phạm vi của cuộc thảo luận này và phù hợp hơn với các câu hỏi SO khác.
NanoWizard

264

Tôi googled cho mã để hợp nhất các thuộc tính đối tượng và kết thúc ở đây. Tuy nhiên vì không có bất kỳ mã nào để hợp nhất đệ quy nên tôi đã tự viết nó. (Có lẽ jQuery mở rộng là đệ quy BTW?) Dù sao đi nữa, hy vọng người khác cũng sẽ thấy nó hữu ích.

(Bây giờ mã không sử dụng 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;
}

Một ví dụ

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

Tạo đối tượng o3 như

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

11
Đẹp, nhưng tôi sẽ làm một bản đồ sâu của các đối tượng đầu tiên. Cách này cũng sẽ được sửa đổi, vì các đối tượng được truyền bằng tham chiếu.
skerit

1
Đây là những gì tôi đang tìm kiếm. Hãy chắc chắn kiểm tra xem obj2.hasOwnProperty (p) trước khi bạn đi đến câu lệnh thử bắt. Nếu không, bạn có thể kết thúc với một số crap khác được hợp nhất từ ​​cấp cao hơn trong chuỗi nguyên mẫu.
Adam

3
Cảm ơn Markus. Lưu ý rằng o1 cũng được sửa đổi bởi MergeRecursive, vì vậy ở cuối, o1 và o3 giống hệt nhau. Nó có thể trực quan hơn nếu bạn không trả lại bất cứ điều gì, vì vậy người dùng biết rằng những gì họ cung cấp (o1) sẽ được sửa đổi và trở thành kết quả. Ngoài ra, trong ví dụ của bạn, có thể đáng để thêm một cái gì đó vào o2 ban đầu không có trong o1, để cho thấy rằng các thuộc tính o2 được chèn vào o1 (một người khác bên dưới đã gửi mã thay thế sao chép ví dụ của bạn, nhưng chúng bị phá vỡ khi o2 không chứa các thuộc tính trong o1 - nhưng của bạn hoạt động trong khía cạnh này).
bánh kếp

7
Đây là một câu trả lời tốt, nhưng một vài lời ngụy biện: 1) Không có logic nên dựa trên các ngoại lệ; trong trường hợp này, bất kỳ ngoại lệ nào được ném sẽ bị bắt với kết quả không thể đoán trước. 2) Tôi đồng ý với nhận xét của @ pancake về việc không trả lại bất cứ điều gì, vì đối tượng ban đầu đã được sửa đổi. Đây là một ví dụ jsFiddle không có logic ngoại lệ: jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery
Jeff Lowery

3
Ngoài ra, obj2 [p] .constructor == Array là cần thiết trong mọi trường hợp có một mảng các đối tượng.
Nhà soạn nhạc

175

Lưu ý rằng underscore.js's extend-method thực hiện điều này trong một lớp lót:

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

36
Ví dụ này tốt khi bạn xử lý các đối tượng ẩn danh, nhưng nếu đây không phải là trường hợp thì nhận xét của webmat trong cảnh báo câu trả lời của jQuery về các đột biến được áp dụng ở đây, vì gạch dưới cũng làm biến đổi đối tượng đích . Giống như câu trả lời của jQuery, để thực hiện việc đột biến này, chỉ cần hợp nhất thành một đối tượng trống:_({}).extend(obj1, obj2);
Abe Voelker

8
Có một cái khác có thể liên quan tùy thuộc vào những gì bạn đang cố gắng đạt được: _.defaults(object, *defaults)"Điền vào các thuộc tính null và không xác định trong đối tượng bằng các giá trị từ các đối tượng mặc định và trả về đối tượng."
Conny

Nhiều người đã bình luận về việc làm biến đổi đối tượng đích, nhưng thay vì có một phương thức tạo một đối tượng mới, một mẫu chung (ví dụ trong võ đường) là nói dojo.mixin (dojo.clone (myobj), {newpropertys: "để thêm to myobj "})
Colin D

7
Gạch dưới có thể lấy số lượng đối tượng tùy ý, vì vậy bạn có thể tránh đột biến đối tượng ban đầu bằng cách thực hiện var mergedObj = _.extend({}, obj1, obj2).
lobati

84

Tương tự như jQuery extend (), bạn có cùng chức năng trong 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 Tôi nghĩ đó là một lựa chọn nếu bạn không muốn thêm lib chỉ cho fn này.
juanmf 22/03/2016

67

Tôi cần hợp nhất các đối tượng ngày hôm nay, và câu hỏi này (và câu trả lời) đã giúp tôi rất nhiều. Tôi đã thử một số câu trả lời, nhưng không có câu trả lời nào phù hợp với nhu cầu của tôi, vì vậy tôi đã kết hợp một số câu trả lời, tự mình thêm một số thứ và đưa ra một chức năng hợp nhất mới. Đây là:

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

Một số ví dụ sử dụng:

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
câu trả lời tốt nhưng tôi nghĩ rằng nếu một tài sản có mặt trong lần đầu tiên và lần thứ hai và bạn đang cố gắng tạo một đối tượng mới bằng cách hợp nhất thứ nhất và thứ hai thì những thứ duy nhất có trong đối tượng thứ nhất sẽ bị mất
Strikers

2
Nhưng đó không phải là hành vi mong đợi sao? Mục đích của hàm này là ghi đè các giá trị theo thứ tự đối số. Tiếp theo sẽ ghi đè lên hiện tại. Làm thế nào bạn có thể bảo tồn các giá trị độc đáo? Từ ví dụ của tôi, bạn mong đợi điều merge({}, t1, { key1: 1 })gì?
Emre Erkan

Kỳ vọng của tôi là chỉ hợp nhất các giá trị không gõ đối tượng.
AGamePlayer 28/2/2015

không thành công nếu kết quả mong đợi như sau: gist.github.com/ceremcem/a54f90923c6e6ec42c7daf24cf2768bd
ceremcem

Điều này làm việc cho tôi trong chế độ nghiêm ngặt cho dự án node.js của tôi. Cho đến nay, đây là câu trả lời tốt nhất trên bài đăng này.
klewis 16/03/18

48

Bạn có thể sử dụng các thuộc tính trải rộng đối tượng Hiện tại một đề xuất ECMAScript giai đoạn 3.

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

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


Hỗ trợ trình duyệt?
Alph.Dev

1
@ Alph.Dev Bạn có biết caniuse.com không?
Connexo

Tôi không thể có được cú pháp 3 chấm để làm việc trong node.js. nút phàn nàn về nó.
klewis

41

Các giải pháp đã cho nên được sửa đổi để kiểm tra source.hasOwnProperty(property)các for..invòng lặp trước khi gán - nếu không, cuối cùng bạn sẽ sao chép các thuộc tính của toàn bộ chuỗi nguyên mẫu, điều hiếm khi được mong muốn ...


40

Hợp nhất các thuộc tính của N đối tượng trong một dòng mã

Một Object.assignphương thức là một phần của tiêu chuẩn ECMAScript 2015 (ES6) và thực hiện chính xác những gì bạn cần. ( IEkhông được hỗ trợ)

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

Phương thức Object.assign () được sử dụng để sao chép các giá trị của tất cả các thuộc tính riêng có thể đếm được từ một hoặc nhiều đối tượng nguồn vào một đối tượng đích.

Đọc thêm...

Các polyfill để hỗ trợ trình duyệt cũ hơn:

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

'sử dụng nghiêm ngặt' làm gì trong các trình duyệt cũ hơn hoặc tại sao nó lại ở đó. các trình duyệt hỗ trợ đối tượng [notifyProperty; chìa khóa; getOwnPropertyDescriptor; vv], không chính xác cũ. Chúng chỉ là phiên bản trước của gen.5 UAs.
Bekim Bacaj

Cảm ơn đã làm rõ rằng Objects.assigntrả về một đối tượng hợp nhất mà các câu trả lời khác không. Đó là phong cách lập trình nhiều chức năng hơn.
Sridhar Sarnobat

36

Hai sau đây có lẽ là một điểm khởi đầu tốt. lodash cũng có chức năng tùy biến cho những nhu cầu đặc biệt đó!

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


4
Xin lưu ý rằng các phương pháp trên làm thay đổi đối tượng ban đầu.
Wtower

Phương thức lodash hoạt động với tôi khi tôi muốn hợp nhất hai đối tượng và giữ các thuộc tính lồng nhau sâu hơn một hoặc hai cấp (thay vì chỉ thay thế / xóa sạch chúng).
SamBrick

đúng @Wtower. Chỉ có một lỗi vì điều này. Tốt nhất hãy đảm bảo rằng nó luôn bắt đầu như _.merge ({}, ...
Martin Cremer

29

Nhân tiện, tất cả những gì bạn đang làm là ghi đè các thuộc tính, không hợp nhất ...

Đây là cách khu vực đối tượng JavaScript thực sự được hợp nhất: Chỉ các khóa trong tođối tượng không phải là đối tượng sẽ bị ghi đè from. Mọi thứ khác sẽ được thực sự hợp nhất . Tất nhiên, bạn có thể thay đổi hành vi này để không ghi đè lên bất cứ thứ gì tồn tại như thế chỉ khi to[n] is undefined, v.v ...

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

Sử dụng:

var merged = realMerge(obj1, obj2);

3
Đã giúp tôi rất nhiều, phần mở rộng js gạch dưới thực sự đã ghi đè lên, thay vì hợp nhất
David Cumps

1
Đáng lưu ý rằng mảng typeof và typeof null sẽ trả về 'đối tượng' và phá vỡ điều này.
Salakar

@ u01jmg3 xem câu trả lời của tôi ở đây: stackoverflow.com/questions/27936772/NH - hàm
isObject

Điều này sẽ phá vỡ nếu bạn sử dụng ở chế độ nghiêm ngặt cho các dự án node.js
klewis 16/03/18

28

Đây là vết đâm của tôi

  1. Hỗ trợ hợp nhất sâu
  2. Không làm thay đổi đối số
  3. Có bất kỳ số lượng đối số
  4. Không mở rộng nguyên mẫu đối tượng
  5. Không phụ thuộc vào thư viện khác ( jQuery , MooTools , Underscore.js , v.v.)
  6. Bao gồm kiểm tra hasOwnProperty
  7. Là ngắn :)

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

Thí dụ:

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

So với những cái khác mà tôi đã thử nghiệm từ trang này, hàm này thực sự là đệ quy (thực hiện hợp nhất sâu) và bắt chước những gì jQuery.extend () thực sự làm tốt. Tuy nhiên, sẽ tốt hơn nếu nó sửa đổi đối tượng / đối số đầu tiên để người dùng có thể quyết định xem họ muốn truyền vào một đối tượng trống {}làm tham số đầu tiên hay có chức năng sửa đổi đối tượng ban đầu. Do đó, nếu bạn thay đổi dst = {}để dst = arguments[0]nó sẽ thay đổi đối tượng đầu tiên bạn vượt qua vào đối tượng sáp nhập
Jasdeep Khalsa

3
Bây giờ nó đã ngừng hoạt động trong chrome. Tôi nghĩ rằng đó là ý tưởng tồi để sử dụng xây dựng như vậy toString.call(src) == '[object Object]'để kiểm tra xem có một đối tượng hay không. typeof srctốt hơn nhiều Hơn nữa, nó biến Mảng thành đối tượng (Tại các leas tôi đã có hành vi như vậy trên chrome mới nhất).
m03geek

Tôi cần phải thực hiện điều này trong một vòng lặp tham lam, tôi đã điểm chuẩn hàm này so với hàm tôi đang sử dụng cho đến nay _.merge (object, [nguồn]) từ lodash (một loại lib gạch dưới): chức năng của bạn nhanh hơn gần gấp đôi cảm ơn bạn đời
Arnaud Bouchot

20

Object.assign ()

Bản thảo năm 2015 (ES6)

Đây là một công nghệ mới, một phần của tiêu chuẩn ECMAScript 2015 (ES6). Thông số kỹ thuật của công nghệ này đã được hoàn thiện, nhưng kiểm tra bảng tương thích để biết tình trạng sử dụng và triển khai trong các trình duyệt khác nhau.

Phương thức Object.assign () được sử dụng để sao chép các giá trị của tất cả các thuộc tính riêng có thể đếm được từ một hoặc nhiều đối tượng nguồn vào một đối tượng đích. Nó sẽ trả về đối tượng đích.

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

Đối với các đối tượng không quá phức tạp, bạn có thể sử dụng 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_/

Xin lưu ý rằng trong ví dụ này "} {" không được xuất hiện trong một chuỗi!


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

hoặc dưới dạng một lớp lót:objMerge = JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)).replace(/\}\{/, ", "));
Rabbi Shuki Gur

1
@Charles, xin lỗi, nhưng "phương pháp này rất nguy hiểm" nhưng cũng thú vị - đây là cách an toàn nhất có thể Thứ nhất nó sử dụng JSON để thực hiện việc đọc nội dung đối tượng, thứ hai, nó sử dụng đối tượng JSON, nghĩa là an toàn nhất máy được tối ưu hóa cho việc sửa lại JSO từ một chuỗi đối tượng tương thích JSON. Và cũng tương thích ngược với IE8, trình duyệt đầu tiên thực hiện tiêu chuẩn của nó. Nó chỉ khiến tôi tự hỏi "tại sao oh tại sao bạn phải nói một điều ngu ngốc như vậy và thoát khỏi nó"?
Bekim Bacaj

Tôi tin rằng các chức năng trong đối tượng sẽ bị hỏng ở đây vì không thể sử dụng chuỗi, nhưng chúng ta có thể làm rõ các nhược điểm khác không?
yeahdixon

18

Có một thư viện được gọi deepmergetrên GitHub : Điều đó dường như đang có một lực kéo. Đó là một độc lập, có sẵn thông qua cả người quản lý gói npm và bower.

Tôi sẽ có xu hướng sử dụng hoặc cải thiện điều này thay vì mã sao chép từ các câu trả lời.


17

Cách tốt nhất để bạn làm điều này là thêm một thuộc tính phù hợp không thể đếm được bằng Object.defineProperty.

Bằng cách này, bạn vẫn có thể lặp lại các thuộc tính đối tượng của mình mà không cần "mở rộng" mới được tạo nếu bạn tạo thuộc tính bằng Object.prototype.extend.

Hy vọng điều này sẽ giúp:

Object.defineProperty (Object.prototype, "extend", {
    vô số: sai,
    giá trị: hàm (từ) {
        var props = Object.getOwnPropertyNames (từ);
        var mệnh = này;
        props.forEach (hàm (tên) {
            if (name in Dest) {
                var Destination = Object.getOwnPropertyDescriptor (từ, tên);
                Object.defineProperty (mệnh, tên, đích);
            }
        });
        trả lại cái này;
    }
});

Một khi bạn đã làm việc đó, bạn có thể làm:

var obj = {
    tên: 'ngăn xếp',
    kết thúc: 'tràn'
}
thay thế var = {
    tên: 'chứng khoán'
};

obj.extend (thay thế);

Tôi vừa viết một bài đăng blog về nó ở đây: http://onemoredigit.com/post/1527191998/extending-objects-in-node-js


Đợi một chút --- mã không hoạt động! :( đã chèn nó vào jsperf để so sánh với các thuật toán hợp nhất khác và mã không thành công - chức năng mở rộng không tồn tại
Jens Roland

16

Bạn chỉ có thể sử dụng jQuery extend

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

jQuery.extend(obj1, obj2);

Bây giờ obj1chứa tất cả các giá trị của obj1obj2


2
obj1 sẽ chứa tất cả các giá trị, không phải obj2. Bạn đang mở rộng đối tượng đầu tiên. jQuery.extend( target [, object1 ] [, objectN ] )
hủy hoại

5
Câu hỏi này không phải về jQuery. JavaScript! = JQuery
Jorge Riv

1
Tôi đã tìm kiếm JavaScript, nhưng đây là một cách tiếp cận đơn giản hơn
Ehsan

13

Nguyên mẫu có điều này:

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

obj1.extend(obj2) sẽ làm những gì bạn muốn.


6
Ý bạn là Object.prototype.extend? Trong mọi trường hợp, không nên mở rộng Object.prototype. -1.
SolutionYogi

Nghĩ về nó, có lẽ nên Object.prototypevà không Object... Ý tưởng tốt hay không, đây là những gì prototype.jskhung làm, và vì vậy nếu bạn đang sử dụng nó hoặc một khung khác phụ thuộc vào nó, Object.prototypesẽ được mở rộng với extend.
ephemient

2
Tôi không có ý định đàn áp bạn nhưng nói rằng nó ổn vì nguyên mẫu là một đối số kém. Prototype.js là phổ biến nhưng điều đó không có nghĩa là chúng là mô hình vai trò về cách làm JavaScript. Kiểm tra đánh giá chi tiết này về thư viện Prototype bởi một chuyên gia JS. dhtmlk Kitchen.com/?carget=/JavaScript/&date=2008/06/17/iêu
SolutionYogi

7
Ai quan tâm nếu nó đúng hay sai? Nếu nó hoạt động thì nó hoạt động. Chờ đợi giải pháp hoàn hảo là điều tuyệt vời trừ khi cố gắng giữ hợp đồng, giành được khách hàng mới hoặc đáp ứng thời hạn. Hãy cho bất cứ ai ở đây đam mê lập trình tiền miễn phí và cuối cùng họ sẽ làm mọi thứ theo cách "đúng".
David

1
Các bạn đã tìm thấy tất cả các bạn ở Object.prototypeđâu? Object.extendsẽ không can thiệp vào các đối tượng của bạn, nó chỉ gắn một chức năng vào một Objectđối tượng. Và obj1.extend(obj2)không đúng cách để kích hoạt chức năng, hãy sử dụng Object.extend(obj1, obj2)insted
artnikpro

13

** Hợp nhất các đối tượng là đơn giản bằng cách sử dụng Object.assignhoặc ...toán tử lây lan **

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

Các đối tượng được hợp nhất từ ​​phải sang trái, điều này có nghĩa là các đối tượng có các thuộc tính giống hệt như các đối tượng bên phải của chúng sẽ bị ghi đè.

Trong ví dụ này obj2.carghi đèobj1.car



10

Tôi mở rộng phương pháp của David Coallier:

  • Đã thêm khả năng hợp nhất nhiều đối tượng
  • Hỗ trợ các đối tượng sâu
  • tham số ghi đè (được phát hiện nếu tham số cuối cùng là boolean)

Nếu ghi đè là sai, không có thuộc tính nào bị ghi đè nhưng thuộc tính mới sẽ được thêm vào.

Cách sử dụng: obj.merge (hợp nhất ... [, ghi đè]);

Đây là mã của tôi:

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

Ví dụ và TestCase:

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

Phương thức bằng của tôi có thể được tìm thấy ở đây: So sánh đối tượng trong JavaScript



9

Trong Ext JS 4 có thể được thực hiện như sau:

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

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

Xem hợp nhất (đối tượng): Đối tượng .


1
Cảnh giác với lỗi EXTJS-9173 trong EXT.Object.merge cho đến sau +2 năm vẫn chưa được giải quyết
Chris

9

Wow .. đây là bài đăng StackOverflow đầu tiên tôi thấy với nhiều trang. Xin lỗi vì đã thêm một "câu trả lời"

Phương pháp này dành cho ES5 & trước đó - có rất nhiều câu trả lời khác giải quyết ES6.

Tôi không thấy bất kỳ đối tượng "sâu" nào hợp nhất sử dụng argumentstài sản. Đây là câu trả lời của tôi - nhỏ gọn & đệ quy , cho phép các đối số không giới hạn được truyền qua:

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

Phần được nhận xét là tùy chọn .. nó sẽ chỉ bỏ qua các đối số được truyền không phải là đối tượng (ngăn ngừa lỗi).

Thí dụ:

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

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

Câu trả lời này bây giờ nhưng là một giọt nước trong đại dương ...


Câu trả lời ngắn nhất mà làm việc tuyệt vời! Nó xứng đáng được nâng cao hơn!
Jens Törnell

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

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

Sử dụng jQuery.extend () - Liên kết

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

Sử dụng _.merge () - Liên kết

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

Sử dụng _.extend () - Liên kết

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

Sử dụng Object.assign () ECMAScript 2015 (ES6) - Liên kết

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

Đầu ra của tất cả

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

Dựa trên câu trả lời của Markusvsync ' , đây là phiên bản mở rộng. Hàm lấy bất kỳ số lượng đối số. Nó có thể được sử dụng để đặt các thuộc tính trên các nút DOM và tạo các bản sao sâu của các giá trị. Tuy nhiên, đối số đầu tiên được đưa ra bằng cách tham khảo.

Để phát hiện nút DOM, hàm isDOMNode () được sử dụng (xem Câu hỏi chồng chéo JavaScript isDOM - Làm thế nào để bạn kiểm tra xem Đối tượng JavaScript có phải là Đối tượng DOM không? )

Nó đã được thử nghiệm trong Opera 11, Firefox 6, Internet Explorer 8 và 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;
}

Vài ví dụ

Đặt bên trongHTML và kiểu của Phần tử HTML

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

Hợp nhất mảng và đối tượng. Lưu ý rằng không xác định có thể được sử dụng để bảo toàn các giá trị trong mảng / đối tượng bên trái.

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

Bất kỳ đối số nào không tạo ra một đối tượng JavaScript (bao gồm null) sẽ bị bỏ qua. Ngoại trừ đối số đầu tiên, các nút DOM cũng bị loại bỏ. Coi chừng đó là các chuỗi, được tạo như String mới () trong thực tế là các đối tượng.

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

Nếu bạn muốn hợp nhất hai đối tượng thành một đối tượng mới (mà không ảnh hưởng đến bất kỳ đối tượng nào trong hai), hãy cung cấp {} làm đối số đầu tiên

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

Chỉnh sửa (bởi ReaperSoon):

Để hợp nhất mảng

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

Bạn nên sử dụng mặc định của lodashDeep

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

5

Với Underscore.js , để hợp nhất một mảng các đối tượng làm:

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

Nó dẫn đến:

Object {a: 1, b: 2, c: 3, d: 4}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.