Làm thế nào để hợp nhất sâu thay vì hợp nhất nông?


337

Cả Object.assignObject lây lan chỉ thực hiện hợp nhất nông.

Một ví dụ về vấn đề:

// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }

Đầu ra là những gì bạn mong đợi. Tuy nhiên nếu tôi thử điều này:

// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }

Thay vì

{ a: { a: 1, b: 1 } }

bạn lấy

{ a: { b: 1 } }

x bị ghi đè hoàn toàn vì cú pháp lây lan chỉ đi sâu một cấp. Điều này cũng tương tự với Object.assign().

Có cách nào để làm việc này không?


sáp nhập sâu giống như sao chép các thuộc tính từ đối tượng này sang đối tượng khác?

2
Không, vì các thuộc tính đối tượng không nên được ghi đè, thay vào đó, mỗi đối tượng con sẽ được hợp nhất vào cùng một mục tiêu nếu nó đã tồn tại.
Mike

ES6 được hoàn thiện và các tính năng mới không còn được thêm vào, AFAIK.
kangax


1
@Oriol yêu cầu jQuery mặc dù ...
m0meni

Câu trả lời:


330

Có ai biết nếu hợp nhất sâu tồn tại trong thông số ES6 / ES7 không?

Không nó không.


21
Vui lòng xem lại lịch sử chỉnh sửa. Tại thời điểm tôi trả lời điều này, câu hỏi là Có ai biết liệu sự hợp nhất sâu có tồn tại trong thông số ES6 / ES7 không? .

37
Câu trả lời này không còn áp dụng cho câu hỏi này - cần được cập nhật hoặc xóa
DonVaughn

13
Câu hỏi không nên được chỉnh sửa ở mức độ này. Chỉnh sửa là để làm rõ. Một câu hỏi mới nên được đăng.
CJ Thompson

170

Tôi biết đây là một vấn đề cũ nhưng giải pháp đơn giản nhất trong ES2015 / ES6 tôi có thể đưa ra thực sự khá đơn giản, sử dụng Object.assign (),

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

/**
 * Simple object check.
 * @param item
 * @returns {boolean}
 */
export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 * Deep merge two objects.
 * @param target
 * @param ...sources
 */
export function mergeDeep(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();

  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (isObject(source[key])) {
        if (!target[key]) Object.assign(target, { [key]: {} });
        mergeDeep(target[key], source[key]);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }

  return mergeDeep(target, ...sources);
}

Ví dụ sử dụng:

mergeDeep(this, { a: { b: { c: 123 } } });
// or
const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});  
console.dir(merged); // { a: 1, b: { c: { d: [Object] } } }

Bạn sẽ tìm thấy một phiên bản bất biến của điều này trong câu trả lời dưới đây.

Lưu ý rằng điều này sẽ dẫn đến đệ quy vô hạn trên các tham chiếu vòng tròn. Có một số câu trả lời tuyệt vời ở đây về cách phát hiện các tham chiếu vòng tròn nếu bạn nghĩ rằng bạn phải đối mặt với vấn đề này.


1
nếu biểu đồ đối tượng của bạn chứa các chu kỳ sẽ dẫn đến đệ quy vô hạn
the8472

2
Tại sao viết điều này: Object.assign(target, { [key]: {} })nếu nó có thể chỉ đơn giản là target[key] = {}?
Jürg Lehni

1
... và target[key] = source[key]thay vìObject.assign(target, { [key]: source[key] });
Jürg Lehni

3
Điều này không hỗ trợ bất kỳ đối tượng không đơn giản trong target. Ví dụ, mergeDeep({a: 3}, {a: {b: 4}})sẽ dẫn đến một Numberđối tượng tăng , rõ ràng là không mong muốn. Ngoài ra, isObjectkhông chấp nhận mảng, nhưng chấp nhận bất kỳ loại đối tượng gốc nào khác, chẳng hạn như Date, không nên sao chép sâu.
RIV

2
Nó không hoạt động với mảng như tôi hiểu?
Vedmant

119

Bạn có thể sử dụng kết hợp Lodash :

var object = {
  'a': [{ 'b': 2 }, { 'd': 4 }]
};

var other = {
  'a': [{ 'c': 3 }, { 'e': 5 }]
};

_.merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }

6
Này mọi người, đây là giải pháp đơn giản và đẹp nhất. Lodash là tuyệt vời, họ nên bao gồm nó như là đối tượng js cốt lõi
Nurbol Alpysbayev

11
Không nên kết quả { 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }?
J. Hesters

Câu hỏi hay. Đó có thể là một câu hỏi riêng biệt hoặc một câu hỏi cho những người duy trì Lodash.
AndrewHenderson

7
Kết quả { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }là chính xác, bởi vì chúng ta hợp nhất các phần tử của một mảng. Phần tử 0của object.a{b: 2}, phần tử 0của other.a{c: 3}. Khi hai cái này được hợp nhất bởi vì chúng có cùng chỉ số mảng, kết quả là { 'b': 2, 'c': 3 }, đó là phần tử 0trong đối tượng mới.
Alexandru Furculita

Tôi thích cái này hơn , nó nhỏ hơn 6 lần.
Solo

101

Vấn đề là không tầm thường khi nói đến các đối tượng lưu trữ hoặc bất kỳ loại đối tượng nào phức tạp hơn một túi giá trị

  • Bạn có gọi một getter để có được một giá trị hoặc bạn sao chép qua mô tả tài sản?
  • Điều gì xảy ra nếu mục tiêu hợp nhất có một setter (thuộc tính riêng hoặc trong chuỗi nguyên mẫu của nó)? Bạn có xem giá trị là đã có hoặc gọi setter để cập nhật giá trị hiện tại không?
  • Bạn có gọi các chức năng sở hữu riêng hoặc sao chép chúng không? Điều gì xảy ra nếu chúng bị ràng buộc chức năng hoặc chức năng mũi tên tùy thuộc vào thứ gì đó trong chuỗi phạm vi của chúng tại thời điểm chúng được xác định?
  • Điều gì nếu nó giống như một nút DOM? Bạn chắc chắn không muốn coi nó là đối tượng đơn giản và chỉ hợp nhất sâu tất cả các thuộc tính của nó vào
  • Làm thế nào để đối phó với các cấu trúc "đơn giản" như mảng hoặc bản đồ hoặc bộ? Xem xét chúng đã có mặt hoặc hợp nhất chúng quá?
  • Làm thế nào để đối phó với tài sản riêng không vô số?
  • những gì về cây con mới? Đơn giản chỉ cần gán bởi tham chiếu hoặc bản sao sâu?
  • Làm thế nào để đối phó với các đối tượng đông lạnh / niêm phong / không thể mở rộng?

Một điều khác cần ghi nhớ: Đồ thị đối tượng có chứa chu kỳ. Nó thường không khó đối phó - chỉ cần giữ một Setđối tượng nguồn đã truy cập - nhưng thường bị lãng quên.

Có lẽ bạn nên viết một hàm hợp nhất sâu chỉ mong đợi các giá trị nguyên thủy và các đối tượng đơn giản - nhiều nhất là các loại mà thuật toán nhân bản có cấu trúc có thể xử lý - dưới dạng các nguồn hợp nhất. Ném nếu nó gặp bất cứ điều gì nó không thể xử lý hoặc chỉ gán bởi tham chiếu thay vì sáp nhập sâu.

Nói cách khác, không có thuật toán phù hợp với một kích thước, bạn có thể tự cuộn hoặc tìm kiếm một phương thức thư viện xảy ra để bao quát các trường hợp sử dụng của bạn.


2
xin lỗi cho các nhà phát triển V8 không thực hiện chuyển khoản "trạng thái tài liệu" an toàn
cần thiết vào

Bạn nêu ra nhiều vấn đề tốt và tôi rất muốn thấy việc thực hiện khuyến nghị của bạn. Vì vậy, tôi đã cố gắng để làm cho một dưới đây. Bạn có thể vui lòng xem và nhận xét? stackoverflow.com/a/48579540/8122487
RaphaMex

66

Đây là phiên bản không thay đổi (không sửa đổi đầu vào) của câu trả lời của @ Salakar. Hữu ích nếu bạn đang làm công cụ lập trình loại chức năng.

export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

export default function mergeDeep(target, source) {
  let output = Object.assign({}, target);
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach(key => {
      if (isObject(source[key])) {
        if (!(key in target))
          Object.assign(output, { [key]: source[key] });
        else
          output[key] = mergeDeep(target[key], source[key]);
      } else {
        Object.assign(output, { [key]: source[key] });
      }
    });
  }
  return output;
}

1
@torazaburo xem bài viết trước của tôi cho hàm isObject
isObject Salakar

đã cập nhật nó. Sau một số thử nghiệm, tôi đã tìm thấy một lỗi với các đối tượng được lồng sâu
CpILL

3
Tên thuộc tính được tính toán, đầu tiên sẽ sử dụng giá trị keylàm tên thuộc tính, sau này sẽ tạo "khóa" tên thuộc tính. Xem: es6-features.org/#ComputingPropertyNames
CpILL

2
trong isObjectbạn không cần phải kiểm tra && item !== nullở cuối, vì dòng bắt đầu với item &&, không có?
phù du

2
Nếu nguồn đã lồng các đối tượng con sâu hơn mục tiêu, thì các đối tượng đó vẫn sẽ tham chiếu cùng các giá trị trong mergedDeepđầu ra (tôi nghĩ). Ví dụ: const target = { a: 1 }; const source = { b: { c: 2 } }; const merged = mergeDeep(target, source); merged.b.c; // 2 source.b.c = 3; merged.b.c; // 3 Đây có phải là một vấn đề? Nó không làm thay đổi đầu vào, nhưng bất kỳ đột biến nào trong tương lai với đầu vào đều có thể làm thay đổi đầu ra và ngược lại w / đột biến với đầu vào đột biến đầu ra. Đối với những gì nó có giá trị, mặc dù, ramda R.merge()có hành vi tương tự.
James Conkling

40

Vì vấn đề này vẫn còn hoạt động, đây là một cách tiếp cận khác:

  • ES6/2015
  • Bất biến (không sửa đổi các đối tượng ban đầu)
  • Xử lý mảng (nối chúng)

/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
  const isObject = obj => obj && typeof obj === 'object';
  
  return objects.reduce((prev, obj) => {
    Object.keys(obj).forEach(key => {
      const pVal = prev[key];
      const oVal = obj[key];
      
      if (Array.isArray(pVal) && Array.isArray(oVal)) {
        prev[key] = pVal.concat(...oVal);
      }
      else if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeep(pVal, oVal);
      }
      else {
        prev[key] = oVal;
      }
    });
    
    return prev;
  }, {});
}

// Test objects
const obj1 = {
  a: 1,
  b: 1, 
  c: { x: 1, y: 1 },
  d: [ 1, 1 ]
}
const obj2 = {
  b: 2, 
  c: { y: 2, z: 2 },
  d: [ 2, 2 ],
  e: 2
}
const obj3 = mergeDeep(obj1, obj2);

// Out
console.log(obj3);


Cái này đẹp đấy. Tuy nhiên, khi chúng ta có mảng với các phần tử lặp lại, chúng được ghép lại (có các phần tử lặp lại). Tôi đã điều chỉnh điều này để lấy một tham số (mảng duy nhất: true / false).
Phi hành gia

1
Để làm cho các mảng độc đáo, bạn có thể thay đổi prev[key] = pVal.concat(...oVal);thànhprev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
Richard Herry

1
Thật đẹp và sạch sẽ !! Chắc chắn câu trả lời tốt nhất ở đây!
538ROMEO

Vinh quang. Điều này cũng chứng minh rằng các mảng được hợp nhất, đó là những gì tôi đang tìm kiếm.
Tschallacka

Đúng, giải pháp @CplLL được cho là không thay đổi nhưng sử dụng tính biến đổi đối tượng thực tế bên trong hàm trong khi sử dụng reduce thì không.
Augustin Riedinger

30

Tôi biết đã có rất nhiều câu trả lời và nhiều ý kiến ​​cho rằng chúng sẽ không hoạt động. Sự đồng thuận duy nhất là nó phức tạp đến mức không ai tạo ra một tiêu chuẩn cho nó . Tuy nhiên, hầu hết các câu trả lời được chấp nhận trong SO đều phơi bày "các thủ thuật đơn giản" được sử dụng rộng rãi. Vì vậy, đối với tất cả chúng ta như tôi không phải là chuyên gia nhưng muốn viết mã an toàn hơn bằng cách nắm bắt thêm một chút về độ phức tạp của javascript, tôi sẽ cố gắng làm sáng tỏ.

Trước khi bị bẩn tay, hãy để tôi làm rõ 2 điểm:

  • [TUYÊN BỐ TỪ CHỐI] Tôi đề xuất một chức năng bên dưới giải quyết cách chúng tôi lặp sâu vào các đối tượng javascript để sao chép và minh họa những gì thường được nhận xét quá ngắn. Nó không sẵn sàng sản xuất. Để rõ ràng, tôi đã cố tình bỏ qua các cân nhắc khác như các đối tượng hình tròn (theo dõi bởi một thuộc tính biểu tượng hoặc không xung đột) , sao chép giá trị tham chiếu hoặc bản sao sâu , đối tượng đích bất biến (lại nhân bản sâu?), Nghiên cứu từng trường hợp cụ thể từng loại đối tượng , nhận / bộ tính qua accessors ... Ngoài ra, tôi đã không thực hiện kiểm tra -although nó important- vì nó không phải là điểm ở đây cả.
  • Tôi sẽ sử dụng bản sao hoặc gán các thuật ngữ thay vì hợp nhất . Bởi vì trong suy nghĩ của tôi, một sự hợp nhất là bảo thủ và sẽ thất bại khi xảy ra xung đột. Ở đây, khi xung đột, chúng tôi muốn nguồn ghi đè lên đích. Thích Object.assignkhông.

Câu trả lời với for..inhoặcObject.keys gây hiểu nhầm

Tạo một bản sao sâu dường như là một cách thực hành cơ bản và phổ biến đến mức chúng ta mong đợi sẽ tìm thấy một lớp lót hoặc, ít nhất, một chiến thắng nhanh chóng thông qua đệ quy đơn giản. Chúng tôi không hy vọng chúng ta nên cần một thư viện hoặc viết một hàm tùy chỉnh 100 dòng.

Khi tôi lần đầu tiên đọc câu trả lời Salakar của , tôi thực sự nghĩ rằng tôi có thể làm tốt hơn và đơn giản hơn (bạn có thể so sánh nó với Object.assigntrên x={a:1}, y={a:{b:1}}). Sau đó tôi đọc câu trả lời của8472 và tôi nghĩ rằng ... không có cách nào dễ dàng, việc cải thiện các câu trả lời đã không đưa chúng ta đi xa.

Hãy để bản sao sâu và đệ quy sang một bên ngay lập tức. Chỉ cần xem xét làm thế nào (sai) mọi người phân tích các thuộc tính để sao chép một đối tượng rất đơn giản.

const y = Object.create(
    { proto : 1 },
    { a: { enumerable: true, value: 1},
      [Symbol('b')] : { enumerable: true, value: 1} } )

Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied

((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!

((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!

Object.keyssẽ bỏ qua các thuộc tính không thể đếm được, thuộc tính khóa ký hiệu riêng và tất cả các thuộc tính của nguyên mẫu. Nó có thể ổn nếu đối tượng của bạn không có bất kỳ trong số đó. Nhưng hãy nhớ rằng Object.assignxử lý các thuộc tính vô số khóa biểu tượng riêng. Vì vậy, bản sao tùy chỉnh của bạn bị mất nở.

for..insẽ cung cấp các thuộc tính của nguồn, của nguyên mẫu của nó và của chuỗi nguyên mẫu đầy đủ mà bạn không muốn nó (hoặc biết về nó). Mục tiêu của bạn có thể kết thúc với quá nhiều thuộc tính, trộn lẫn các thuộc tính nguyên mẫu và thuộc tính riêng.

Nếu bạn đang viết một hàm mục đích chung và bạn không sử dụng Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbolshay Object.getPrototypeOf, có lẽ bạn đang nhất làm nó sai.

Những điều cần xem xét trước khi viết chức năng của bạn

Trước tiên, hãy chắc chắn rằng bạn hiểu đối tượng Javascript là gì. Trong Javascript, một đối tượng được tạo từ các thuộc tính của chính nó và một đối tượng nguyên mẫu (cha). Đối tượng nguyên mẫu lần lượt được làm bằng các thuộc tính của chính nó và một đối tượng nguyên mẫu. Và như vậy, xác định một chuỗi nguyên mẫu.

Một thuộc tính là một cặp khóa ( stringhoặc symbol) và mô tả ( valuehoặc get/ setaccessor, và các thuộc tính nhưenumerable ).

Cuối cùng, có nhiều loại đối tượng . Bạn có thể muốn xử lý khác nhau một đối tượng Đối tượng từ một đối tượng Ngày hoặc đối tượng Chức năng.

Vì vậy, viết bản sao sâu của bạn, bạn nên trả lời ít nhất những câu hỏi sau:

  1. Những gì tôi xem xét sâu (thích hợp cho tra cứu đệ quy) hoặc căn hộ?
  2. Những thuộc tính nào tôi muốn sao chép? (liệt kê / không liệt kê, khóa chuỗi / khóa ký hiệu, thuộc tính riêng / thuộc tính riêng của nguyên mẫu, giá trị / mô tả ...)

Ví dụ của tôi, tôi cho rằng chỉ có các object Objects là sâu , bởi vì các đối tượng khác được tạo bởi các hàm tạo khác có thể không phù hợp với giao diện sâu. Tùy chỉnh từ SO này .

function toType(a) {
    // Get fine type (object, array, function, null, error, date ...)
    return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}

function isDeepObject(obj) {
    return "Object" === toType(obj);
}

Và tôi đã tạo một optionsđối tượng để chọn những gì cần sao chép (cho mục đích demo).

const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};

Chức năng đề xuất

Bạn có thể kiểm tra nó trong plunker này .

function deepAssign(options) {
    return function deepAssignWithOptions (target, ...sources) {
        sources.forEach( (source) => {

            if (!isDeepObject(source) || !isDeepObject(target))
                return;

            // Copy source's own properties into target's own properties
            function copyProperty(property) {
                const descriptor = Object.getOwnPropertyDescriptor(source, property);
                //default: omit non-enumerable properties
                if (descriptor.enumerable || options.nonEnum) {
                    // Copy in-depth first
                    if (isDeepObject(source[property]) && isDeepObject(target[property]))
                        descriptor.value = deepAssign(options)(target[property], source[property]);
                    //default: omit descriptors
                    if (options.descriptors)
                        Object.defineProperty(target, property, descriptor); // shallow copy descriptor
                    else
                        target[property] = descriptor.value; // shallow copy value only
                }
            }

            // Copy string-keyed properties
            Object.getOwnPropertyNames(source).forEach(copyProperty);

            //default: omit symbol-keyed properties
            if (options.symbols)
                Object.getOwnPropertySymbols(source).forEach(copyProperty);

            //default: omit prototype's own properties
            if (options.proto)
                // Copy souce prototype's own properties into target prototype's own properties
                deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
                    Object.getPrototypeOf(target),
                    Object.getPrototypeOf(source)
                );

        });
        return target;
    }
}

Điều đó có thể được sử dụng như thế này:

const x = { a: { a: 1 } },
      y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }

13

Tôi sử dụng lodash:

import _ = require('lodash');
value = _.merge(value1, value2);

2
Lưu ý rằng hợp nhất sẽ thay đổi đối tượng, nếu bạn muốn một cái gì đó không làm thay đổi đối tượng, thì _cloneDeep(value1).merge(value2)
tắc kè

3
@geckos Bạn có thể làm _.merge ({}, value1, value2)
Spenhouet

10

Đây là cách triển khai TypeScript:

export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T  => {
  if (!sources.length) {
    return target;
  }
  const source = sources.shift();
  if (source === undefined) {
    return target;
  }

  if (isMergebleObject(target) && isMergebleObject(source)) {
    Object.keys(source).forEach(function(key: string) {
      if (isMergebleObject(source[key])) {
        if (!target[key]) {
          target[key] = {};
        }
        mergeObjects(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    });
  }

  return mergeObjects(target, ...sources);
};

const isObject = (item: any): boolean => {
  return item !== null && typeof item === 'object';
};

const isMergebleObject = (item): boolean => {
  return isObject(item) && !Array.isArray(item);
};

Và bài kiểm tra đơn vị:

describe('merge', () => {
  it('should merge Objects and all nested Ones', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
    const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
    const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
    expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
  });
  it('should behave like Object.assign on the top level', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C'};
    const obj2 = { a: undefined, b: { b1: 'B1'}};
    expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
  });
  it('should not merge array values, just override', () => {
    const obj1 = {a: ['A', 'B']};
    const obj2 = {a: ['C'], b: ['D']};
    expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
  });
  it('typed merge', () => {
    expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
      .toEqual(new TestPosition(1, 1));
  });
});

class TestPosition {
  constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}


8

Tôi muốn trình bày một thay thế ES5 khá đơn giản. Hàm được 2 tham số - targetsourceđó phải là kiểu "đối tượng". Targetsẽ là đối tượng kết quả. Targetgiữ tất cả các thuộc tính ban đầu của nó nhưng giá trị của chúng có thể được sửa đổi.

function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
  if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
  if(prop in target) { // handling merging of two properties with equal names
    if(typeof target[prop] !== 'object') {
      target[prop] = source[prop];
    } else {
      if(typeof source[prop] !== 'object') {
        target[prop] = source[prop];
      } else {
        if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
          target[prop] = target[prop].concat(source[prop]);
        } else { // two objects get merged recursively
          target[prop] = deepMerge(target[prop], source[prop]); 
        } 
      }  
    }
  } else { // new properties get added to target
    target[prop] = source[prop]; 
  }
}
return target;
}

các trường hợp:

  • nếu targetkhông có sourcetài sản, hãy targetlấy nó;
  • nếu targetkhông có thuộc sourcetính và target& sourcekhông phải là cả hai đối tượng (3 trường hợp trong số 4), thì thuộc targettính của nó bị chồng chéo;
  • nếu targetkhông có thuộc sourcetính và cả hai đều là đối tượng / mảng (1 trường hợp còn lại), thì đệ quy xảy ra hợp nhất hai đối tượng (hoặc nối hai mảng);

cũng xem xét những điều sau đây :

  1. mảng + obj = mảng
  2. obj + mảng = obj
  3. obj + obj = obj (sáp nhập đệ quy)
  4. mảng + mảng = mảng (concat)

Nó có thể dự đoán được, hỗ trợ các kiểu nguyên thủy cũng như các mảng và các đối tượng. Cũng như chúng ta có thể hợp nhất 2 đối tượng, tôi nghĩ rằng chúng ta có thể hợp nhất hơn 2 qua giảm chức năng.

hãy xem một ví dụ (và chơi xung quanh nó nếu bạn muốn) :

var a = {
   "a_prop": 1,
   "arr_prop": [4, 5, 6],
   "obj": {
     "a_prop": {
       "t_prop": 'test'
     },
     "b_prop": 2
   }
};

var b = {
   "a_prop": 5,
   "arr_prop": [7, 8, 9],
   "b_prop": 15,
   "obj": {
     "a_prop": {
       "u_prop": false
     },
     "b_prop": {
        "s_prop": null
     }
   }
};

function deepMerge(target, source) {
    if(typeof target !== 'object' || typeof source !== 'object') return false;
    for(var prop in source) {
    if(!source.hasOwnProperty(prop)) continue;
      if(prop in target) {
        if(typeof target[prop] !== 'object') {
          target[prop] = source[prop];
        } else {
          if(typeof source[prop] !== 'object') {
            target[prop] = source[prop];
          } else {
            if(target[prop].concat && source[prop].concat) {
              target[prop] = target[prop].concat(source[prop]);
            } else {
              target[prop] = deepMerge(target[prop], source[prop]); 
            } 
          }  
        }
      } else {
        target[prop] = source[prop]; 
      }
    }
  return target;
}

console.log(deepMerge(a, b));

Có một hạn chế - độ dài ngăn xếp cuộc gọi của trình duyệt. Các trình duyệt hiện đại sẽ đưa ra một lỗi ở một mức đệ quy thực sự sâu sắc (nghĩ về hàng ngàn cuộc gọi lồng nhau). Ngoài ra, bạn có thể tự do xử lý các tình huống như mảng + đối tượng, v.v. như bạn muốn bằng cách thêm các điều kiện mới và kiểm tra loại.


8

Đây là một giải pháp ES6 khác, hoạt động với các đối tượng và mảng.

function deepMerge(...sources) {
  let acc = {}
  for (const source of sources) {
    if (source instanceof Array) {
      if (!(acc instanceof Array)) {
        acc = []
      }
      acc = [...acc, ...source]
    } else if (source instanceof Object) {
      for (let [key, value] of Object.entries(source)) {
        if (value instanceof Object && key in acc) {
          value = deepMerge(acc[key], value)
        }
        acc = { ...acc, [key]: value }
      }
    }
  }
  return acc
}

3
cái này đã được thử nghiệm và / hoặc một phần của thư viện, trông rất đẹp nhưng rất thích đảm bảo rằng nó đã được chứng minh phần nào.


7

Có cách nào để làm việc này không?

Nếu các thư viện npm có thể được sử dụng như một giải pháp, đối tượng hợp nhất-nâng cao từ bạn thực sự cho phép hợp nhất sâu các đối tượng và tùy chỉnh / ghi đè mọi hành động hợp nhất duy nhất bằng cách sử dụng chức năng gọi lại quen thuộc. Ý tưởng chính của nó không chỉ là sự hợp nhất sâu sắc - điều gì xảy ra với giá trị khi hai khóa giống nhau ? Thư viện này đảm nhận việc đó - khi hai phím xung đột, object-merge-advancedcân các loại, nhằm giữ lại càng nhiều dữ liệu càng tốt sau khi hợp nhất:

hợp nhất khóa đối tượng cân các loại giá trị khóa để giữ lại càng nhiều dữ liệu càng tốt

Khóa của đối số đầu tiên được đánh dấu # 1, đối số thứ hai - # 2. Tùy thuộc vào từng loại, một loại được chọn cho giá trị của khóa kết quả. Trong sơ đồ, "một đối tượng" có nghĩa là một đối tượng đơn giản (không phải mảng, v.v.).

Khi các khóa không đụng độ, tất cả đều nhập kết quả.

Từ đoạn mã ví dụ của bạn, nếu bạn đã sử dụng object-merge-advancedđể hợp nhất đoạn mã của mình:

const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
//      a: {
//        a: 1,
//        b: 1
//      }
//    }

Thuật toán của nó đi qua đệ quy tất cả các khóa đối tượng đầu vào, so sánh và xây dựng và trả về kết quả hợp nhất mới.


6

Hàm sau tạo một bản sao sâu của các đối tượng, nó bao gồm sao chép nguyên thủy, mảng cũng như đối tượng

 function mergeDeep (target, source)  {
    if (typeof target == "object" && typeof source == "object") {
        for (const key in source) {
            if (source[key] === null && (target[key] === undefined || target[key] === null)) {
                target[key] = null;
            } else if (source[key] instanceof Array) {
                if (!target[key]) target[key] = [];
                //concatenate arrays
                target[key] = target[key].concat(source[key]);
            } else if (typeof source[key] == "object") {
                if (!target[key]) target[key] = {};
                this.mergeDeep(target[key], source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

6

Một giải pháp đơn giản với ES5 (ghi đè giá trị hiện có):

function merge(current, update) {
  Object.keys(update).forEach(function(key) {
    // if update[key] exist, and it's not a string or array,
    // we go in one level deeper
    if (current.hasOwnProperty(key) 
        && typeof current[key] === 'object'
        && !(current[key] instanceof Array)) {
      merge(current[key], update[key]);

    // if update[key] doesn't exist in current, or it's a string
    // or array, then assign/overwrite current[key] to update[key]
    } else {
      current[key] = update[key];
    }
  });
  return current;
}

var x = { a: { a: 1 } }
var y = { a: { b: 1 } }

console.log(merge(x, y));


đúng như những gì tôi cần - es6 đã gây ra sự cố trong quá trình xây dựng - sự thay thế es5 này là quả bom
danday74

5

Hầu hết các ví dụ ở đây có vẻ quá phức tạp, tôi đang sử dụng một trong TypeScript do tôi tạo, tôi nghĩ rằng nó sẽ bao gồm hầu hết các trường hợp (Tôi đang xử lý các mảng như dữ liệu thông thường, chỉ thay thế chúng).

const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);

export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
  const isDeep = (prop: string) =>
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...(target as Object),
    ...(replaced as Object)
  } as A & B;
};

Điều tương tự trong JS đơn giản, chỉ trong trường hợp:

const isObject = item => typeof item === 'object' && !Array.isArray(item);

const merge = (target, source) => {
  const isDeep = prop => 
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...target,
    ...replaced
  };
};

Dưới đây là các trường hợp thử nghiệm của tôi để cho thấy cách bạn có thể sử dụng nó

describe('merge', () => {
  context('shallow merges', () => {
    it('merges objects', () => {
      const a = { a: 'discard' };
      const b = { a: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test' });
    });
    it('extends objects', () => {
      const a = { a: 'test' };
      const b = { b: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
    });
    it('extends a property with an object', () => {
      const a = { a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
    it('replaces a property with an object', () => {
      const a = { b: 'whatever', a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
  });

  context('deep merges', () => {
    it('merges objects', () => {
      const a = { test: { a: 'discard', b: 'test' }  };
      const b = { test: { a: 'test' } } ;
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends objects', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: 'test' } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends a property with an object', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
    it('replaces a property with an object', () => {
      const a = { test: { b: 'whatever', a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
  });
});

Xin vui lòng cho tôi biết nếu bạn nghĩ rằng tôi đang thiếu một số chức năng.


5

Nếu bạn muốn có một lớp lót mà không yêu cầu một thư viện khổng lồ như lodash, tôi khuyên bạn nên sử dụng deepmerge . ( npm install deepmerge)

Sau đó, bạn có thể làm

deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });

để có được

{ a: 2, b: 2, c: 3, d: 3 }

Điều tuyệt vời là nó đi kèm với các kiểu chữ cho TypeScript ngay lập tức. Nó cũng cho phép hợp nhất các mảng . Một giải pháp toàn diện thực sự này là.


4

Chúng ta có thể sử dụng $ .extend (true, object1, object2) để hợp nhất sâu. Giá trị đúng biểu thị hợp nhất hai đối tượng đệ quy, sửa đổi đối tượng đầu tiên.

$ extend (đúng, đích, đối tượng)


9
Người hỏi không bao giờ chỉ ra rằng họ đang sử dụng jquery và dường như đang yêu cầu một giải pháp javascript gốc.
Te JoE

Đây là một cách rất đơn giản để làm điều này và nó hoạt động. Một giải pháp khả thi mà tôi sẽ xem xét nếu tôi là người hỏi câu hỏi này. :)
kashiraja

Đây là một câu trả lời rất hay nhưng thiếu một liên kết đến mã nguồn tới jQuery. jQuery có rất nhiều người làm việc trong dự án và họ đã dành thời gian để sao chép sâu hoạt động đúng. Ngoài ra, mã nguồn khá "đơn giản": github.com/jquery/jquery/blob/master/src/core.js#L125 "Simple" nằm trong dấu ngoặc kép vì nó bắt đầu trở nên phức tạp khi đào sâu vào jQuery.isPlainObject(). Điều đó cho thấy sự phức tạp của việc xác định liệu một thứ gì đó có phải là một vật thể đơn giản hay không, mà hầu hết các câu trả lời ở đây đều bỏ lỡ bởi một cú sút xa. Đoán xem jQuery được viết bằng ngôn ngữ nào?
CubicleSoft

4

Ở đây thẳng tiến, giải pháp đơn giản hoạt động như Object.assignchỉ là deeep, và làm việc cho một mảng, mà không cần sửa đổi gì

function deepAssign(target, ...sources) {
    for( source of sources){
        for(let k in source){
            let vs = source[k], vt = target[k];
            if(Object(vs)== vs && Object(vt)===vt ){
                target[k] = deepAssign(vt, vs)
                continue;
            }
            target[k] = source[k];
        }    
    }
    return target;
}

Thí dụ

x = { a: { a: 1 }, b:[1,2] };
y = { a: { b: 1 }, b:[3] };
z = {c:3,b:[,,,4]}
x = deepAssign(x,y,z)
// x will be
x ==  {
  "a": {
    "a": 1,
    "b": 1
  },
  "b": [    1,    2,    null,    4  ],
  "c": 3
}


3

Tôi đã gặp vấn đề này khi tải trạng thái redux lưu trữ. Nếu tôi chỉ tải trạng thái được lưu trong bộ nhớ cache, tôi sẽ gặp lỗi cho phiên bản ứng dụng mới có cấu trúc trạng thái được cập nhật.

Nó đã được đề cập, lodash cung cấp mergechức năng, mà tôi đã sử dụng:

const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);

3

Nhiều câu trả lời sử dụng hàng chục dòng mã hoặc yêu cầu thêm thư viện mới vào dự án, nhưng nếu bạn sử dụng đệ quy thì đây chỉ là 4 dòng mã.

function merge(current, updates) {
  for (key of Object.keys(updates)) {
    if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
    else merge(current[key], updates[key]);
  }
  return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));

Xử lý mảng: Phiên bản trên ghi đè các giá trị mảng cũ bằng giá trị mới. Nếu bạn muốn nó giữ các giá trị mảng cũ và thêm các giá trị mới, chỉ cần thêm một else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])khối phía trên số elseliệu và bạn đã thiết lập xong.


1
Tôi thích nó nhưng nó cần một kiểm tra đơn giản không xác định cho 'hiện tại' hoặc nếu không {foo: không xác định} không hợp nhất. Chỉ cần thêm một if (hiện tại) trước vòng lặp for.
Andreas Pardeike

Cảm ơn lời đề nghị
Vincent

2

Đây là một cái khác tôi vừa viết rằng hỗ trợ mảng. Nó che giấu chúng.

function isObject(obj) {
    return obj !== null && typeof obj === 'object';
}


function isPlainObject(obj) {
    return isObject(obj) && (
        obj.constructor === Object  // obj = {}
        || obj.constructor === undefined // obj = Object.create(null)
    );
}

function mergeDeep(target, ...sources) {
    if (!sources.length) return target;
    const source = sources.shift();

    if(Array.isArray(target)) {
        if(Array.isArray(source)) {
            target.push(...source);
        } else {
            target.push(source);
        }
    } else if(isPlainObject(target)) {
        if(isPlainObject(source)) {
            for(let key of Object.keys(source)) {
                if(!target[key]) {
                    target[key] = source[key];
                } else {
                    mergeDeep(target[key], source[key]);
                }
            }
        } else {
            throw new Error(`Cannot merge object with non-object`);
        }
    } else {
        target = source;
    }

    return mergeDeep(target, ...sources);
};

2

Sử dụng chức năng này:

merge(target, source, mutable = false) {
        const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
        for (const prop in source) {
            if (target[prop] == null || typeof target[prop] === 'undefined') {
                newObj[prop] = source[prop];
            } else if (Array.isArray(target[prop])) {
                newObj[prop] = source[prop] || target[prop];
            } else if (target[prop] instanceof RegExp) {
                newObj[prop] = source[prop] || target[prop];
            } else {
                newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
            }
        }
        return newObj;
    }

2

Ramda, một thư viện đẹp của các hàm javascript có mergeDeepLeft và mergeDeepRight. Bất kỳ trong số này làm việc khá tốt cho vấn đề này. Xin hãy xem tài liệu ở đây: https://ramdajs.com/docs/#mergeDeepLeft

Đối với ví dụ cụ thể trong câu hỏi, chúng ta có thể sử dụng:

import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}

2
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
  for (const prop in source) {
    if (!source.hasOwnProperty(prop)) {
      continue;
    }

    if (source[prop] === null) {
      // property is null
      dest[prop] = source[prop];
      continue;
    }

    if (typeof source[prop] === 'object') {
      // if property is object let's dive into in
      if (Array.isArray(source[prop])) {
        dest[prop] = [];
      } else {
        if (!dest.hasOwnProperty(prop)
        || typeof dest[prop] !== 'object'
        || dest[prop] === null || Array.isArray(dest[prop])
        || !Object.keys(dest[prop]).length) {
          dest[prop] = {};
        }
      }
      recursivelyMoveProperties(source[prop], dest[prop]);
      continue;
    }

    // property is simple type: string, number, e.t.c
    dest[prop] = source[prop];
  }
  return dest;
}

Bài kiểm tra đơn vị:

describe('recursivelyMoveProperties', () => {
    it('should copy properties correctly', () => {
      const source: any = {
        propS1: 'str1',
        propS2: 'str2',
        propN1: 1,
        propN2: 2,
        propA1: [1, 2, 3],
        propA2: [],
        propB1: true,
        propB2: false,
        propU1: null,
        propU2: null,
        propD1: undefined,
        propD2: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subN1: 21,
          subN2: 22,
          subA1: [21, 22, 23],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      let dest: any = {
        propS2: 'str2',
        propS3: 'str3',
        propN2: -2,
        propN3: 3,
        propA2: [2, 2],
        propA3: [3, 2, 1],
        propB2: true,
        propB3: false,
        propU2: 'not null',
        propU3: null,
        propD2: 'defined',
        propD3: undefined,
        propO2: {
          subS2: 'inv22',
          subS3: 'sub23',
          subN2: -22,
          subN3: 23,
          subA2: [5, 5, 5],
          subA3: [31, 32, 33],
          subB2: false,
          subB3: true,
          subU2: 'not null --- ',
          subU3: null,
          subD2: ' not undefined ----',
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      dest = recursivelyMoveProperties(source, dest);

      expect(dest).toEqual({
        propS1: 'str1',
        propS2: 'str2',
        propS3: 'str3',
        propN1: 1,
        propN2: 2,
        propN3: 3,
        propA1: [1, 2, 3],
        propA2: [],
        propA3: [3, 2, 1],
        propB1: true,
        propB2: false,
        propB3: false,
        propU1: null,
        propU2: null,
        propU3: null,
        propD1: undefined,
        propD2: undefined,
        propD3: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subS3: 'sub23',
          subN1: 21,
          subN2: 22,
          subN3: 23,
          subA1: [21, 22, 23],
          subA2: [],
          subA3: [31, 32, 33],
          subB1: false,
          subB2: true,
          subB3: true,
          subU1: null,
          subU2: null,
          subU3: null,
          subD1: undefined,
          subD2: undefined,
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      });
    });
  });

2

Tôi chỉ tìm thấy giải pháp 2 dòng để hợp nhất sâu trong javascript. Hãy cho tôi biết làm thế nào điều này làm việc cho bạn.

const obj1 = { a: { b: "c", x: "y" } }
const obj2 = { a: { b: "d", e: "f" } }
temp = Object.assign({}, obj1, obj2)
Object.keys(temp).forEach(key => {
    temp[key] = (typeof temp[key] === 'object') ? Object.assign(temp[key], obj1[key], obj2[key]) : temp[key])
}
console.log(temp)

Đối tượng Temp sẽ in {a: {b: 'd', e: 'f', x: 'y'}}


1
Điều này không làm hợp nhất sâu thực tế. Nó sẽ thất bại với merge({x:{y:{z:1}}}, {x:{y:{w:2}}}). Il cũng sẽ không cập nhật các giá trị hiện có trong obj1 nếu obj2 cũng có chúng, ví dụ như với merge({x:{y:1}}, {x:{y:2}}).
Oreilles

1

Đôi khi bạn không cần hợp nhất sâu sắc, ngay cả khi bạn nghĩ như vậy. Ví dụ: nếu bạn có một cấu hình mặc định với các đối tượng lồng nhau và bạn muốn mở rộng nó sâu với cấu hình của riêng bạn, bạn có thể tạo một lớp cho điều đó. Khái niệm này là rất đơn giản:

function AjaxConfig(config) {

  // Default values + config

  Object.assign(this, {
    method: 'POST',
    contentType: 'text/plain'
  }, config);

  // Default values in nested objects

  this.headers = Object.assign({}, this.headers, { 
    'X-Requested-With': 'custom'
  });
}

// Define your config

var config = {
  url: 'https://google.com',
  headers: {
    'x-client-data': 'CI22yQEI'
  }
};

// Extend the default values with your own
var fullMergedConfig = new AjaxConfig(config);

// View in DevTools
console.log(fullMergedConfig);

Bạn có thể chuyển đổi nó thành một hàm (không phải là hàm tạo).


1

Đây là một sự hợp nhất sâu giá rẻ sử dụng ít mã như tôi có thể nghĩ đến. Mỗi nguồn ghi đè lên tài sản trước đó khi nó tồn tại.

const { keys } = Object;

const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
  isObject(a) && isObject(b)
    ? deepMerge(a, b)
    : isObject(a) && !isObject(b)
    ? a
    : b;

const coalesceByKey = source => (acc, key) =>
  (acc[key] && source[key]
    ? (acc[key] = merge(acc[key], source[key]))
    : (acc[key] = source[key])) && acc;

/**
 * Merge all sources into the target
 * overwriting primitive values in the the accumulated target as we go (if they already exist)
 * @param {*} target
 * @param  {...any} sources
 */
const deepMerge = (target, ...sources) =>
  sources.reduce(
    (acc, source) => keys(source).reduce(coalesceByKey(source), acc),
    target
  );

console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));

1

Tôi đang sử dụng chức năng ngắn sau đây cho các đối tượng hợp nhất sâu.
Đó là công việc tốt cho tôi.
Tác giả hoàn toàn giải thích cách nó hoạt động ở đây.

/*!
 * Merge two or more objects together.
 * (c) 2017 Chris Ferdinandi, MIT License, https://gomakethings.com
 * @param   {Boolean}  deep     If true, do a deep (or recursive) merge [optional]
 * @param   {Object}   objects  The objects to merge together
 * @returns {Object}            Merged values of defaults and options
 * 
 * Use the function as follows:
 * let shallowMerge = extend(obj1, obj2);
 * let deepMerge = extend(true, obj1, obj2)
 */

var extend = function () {

    // Variables
    var extended = {};
    var deep = false;
    var i = 0;

    // Check if a deep merge
    if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) {
        deep = arguments[0];
        i++;
    }

    // Merge the object into the extended object
    var merge = function (obj) {
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                // If property is an object, merge properties
                if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
                    extended[prop] = extend(extended[prop], obj[prop]);
                } else {
                    extended[prop] = obj[prop];
                }
            }
        }
    };

    // Loop through each object and conduct a merge
    for (; i < arguments.length; i++) {
        merge(arguments[i]);
    }

    return extended;

};

Mặc dù liên kết này có thể trả lời câu hỏi, tốt hơn là bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi. - Từ đánh giá
Chris Camaratta

Xin chào @ChrisCamaratta. Không chỉ là phần thiết yếu ở đây, tất cả đều ở đây - chức năng và cách sử dụng nó. Vì vậy, đây chắc chắn không phải là một liên kết chỉ trả lời. Đây là chức năng tôi đã và đang sử dụng để hợp nhất sâu các đối tượng. Liên kết chỉ khi bạn muốn các tác giả giải thích về cách thức hoạt động của nó. Tôi cảm thấy nó sẽ là một sự bất đồng với cộng đồng để cố gắng giải thích các hoạt động tốt hơn so với tác giả dạy JavaScript. Cảm ơn các bình luận.
John Shear

Huh. Hoặc tôi đã bỏ lỡ nó hoặc mã không xuất hiện trong giao diện người đánh giá khi tôi xem xét nó. Tôi đồng ý đây là một câu trả lời chất lượng. Nó sẽ xuất hiện những người đánh giá khác vượt qua đánh giá ban đầu của tôi để tôi nghĩ rằng bạn ổn. Xin lỗi cho cờ cảm hứng.
Chris Camaratta

Tuyệt quá! @ChrisCamaratta, Cảm ơn đã giúp tôi hiểu chuyện gì đã xảy ra.
John Shear
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.