วิธีการผสานลึกแทนที่จะรวมตื้น?


340

ทั้งObject.assignและObject Spreadจะทำการผสานตื้นเท่านั้น

ตัวอย่างของปัญหา:

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

ผลลัพธ์คือสิ่งที่คุณคาดหวัง อย่างไรก็ตามถ้าฉันลองทำสิ่งนี้:

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

แทน

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

คุณได้รับ

{ a: { b: 1 } }

x ถูกเขียนทับอย่างสมบูรณ์เพราะไวยากรณ์การแพร่กระจายไปลึกเพียงหนึ่งระดับ Object.assign()นี้เป็นเช่นเดียวกับ

มีวิธีทำเช่นนี้หรือไม่?


การผสานแบบลึกเหมือนกับการคัดลอกคุณสมบัติจากวัตถุหนึ่งไปอีกวัตถุหนึ่งหรือไม่

2
ไม่ได้เนื่องจากคุณสมบัติของวัตถุไม่ควรเขียนทับ แต่ควรจะรวมวัตถุลูกแต่ละอันไว้ในรายการย่อยเดียวกันกับเป้าหมายหากมีอยู่แล้ว
Mike

ES6 ได้รับการสรุปและไม่มีการเพิ่มฟีเจอร์ใหม่ AFAIK
kangax


1
@Oriol ต้องการ jQuery แม้ว่า ...
m0meni

คำตอบ:


331

ไม่มีใครทราบว่ามีการรวมกันอย่างลึกซึ้งในข้อมูลจำเพาะ ES6 / ES7 หรือไม่?

ไม่มันไม่


21
โปรดตรวจสอบประวัติการแก้ไข ในขณะที่ฉันตอบคำถามนี้มีใครบ้างที่รู้ว่ามีการรวมกันอย่างลึกล้ำในข้อมูลจำเพาะ ES6 / ES7 หรือไม่? .

37
คำตอบนี้ใช้ไม่ได้กับคำถามนี้ - ควรอัปเดตหรือลบ
DonVaughn

13
ไม่ควรแก้ไขคำถามในระดับนี้ การแก้ไขเป็นการชี้แจง ควรมีการโพสต์คำถามใหม่
CJ Thompson

171

ฉันรู้ว่านี่เป็นปัญหาเล็กน้อย แต่ทางออกที่ง่ายที่สุดใน ES2015 / ES6 ที่ฉันสามารถทำได้จริง ๆ แล้วค่อนข้างง่ายโดยใช้ Object.assign ()

หวังว่านี่จะช่วย:

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

ตัวอย่างการใช้งาน:

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

คุณจะพบเวอร์ชันนี้ที่ไม่เปลี่ยนรูปในคำตอบด้านล่าง

โปรดทราบว่าสิ่งนี้จะนำไปสู่การเรียกซ้ำแบบไม่สิ้นสุดในการอ้างอิงแบบวงกลม มีคำตอบที่ดีเกี่ยวกับวิธีการตรวจสอบการอ้างอิงแบบวงกลมหากคุณคิดว่าคุณจะพบปัญหานี้


1
หากกราฟวัตถุของคุณมีรอบที่จะนำไปสู่การเรียกซ้ำแบบไม่สิ้นสุด
the8472

item !== nullไม่ควรต้องอยู่ภายในisObjectเพราะitemได้รับการตรวจสอบความจริงแล้วตั้งแต่เริ่มต้นของเงื่อนไข
mcont

2
ทำไมเขียนนี้: Object.assign(target, { [key]: {} })ถ้ามันก็อาจจะเป็นtarget[key] = {}?
Jürg Lehni

1
... และtarget[key] = source[key]แทนObject.assign(target, { [key]: source[key] });
Jürg Lehni

3
นี้ไม่สนับสนุนวัตถุใด ๆ targetที่ไม่ธรรมดา ตัวอย่างเช่นmergeDeep({a: 3}, {a: {b: 4}})จะส่งผลให้Numberวัตถุเพิ่มซึ่งไม่ต้องการชัดเจน นอกจากนี้isObjectไม่ยอมรับอาร์เรย์ แต่ยอมรับชนิดวัตถุดั้งเดิมอื่น ๆ เช่นDateซึ่งไม่ควรคัดลอกแบบลึก
riv

122

คุณสามารถใช้Lodash merge :

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
เฮ้คนนี่เป็นทางออกที่ง่ายและสวยงามที่สุด Lodash ยอดเยี่ยมพวกเขาควรจะรวมมันไว้ใน core js object
Nurbol Alpysbayev

11
ผลลัพธ์ที่ควรจะเป็น{ 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }ไม่ได้?
J. Hesters

คำถามที่ดี. นั่นอาจเป็นคำถามแยกต่างหากหรือคำถามหนึ่งข้อสำหรับผู้ดูแล Lodash
AndrewHenderson

7
ผลลัพธ์{ 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }นั้นถูกต้องเพราะเรากำลังรวมองค์ประกอบของอาร์เรย์ องค์ประกอบ0ของการobject.aมี{b: 2}องค์ประกอบ0ของการมีother.a {c: 3}เมื่อทั้งสองนี้ถูกผสานเพราะพวกเขามีดัชนีอาร์เรย์เดียวกันผลลัพธ์คือ{ 'b': 2, 'c': 3 }ซึ่งเป็นองค์ประกอบ0ในวัตถุใหม่
Alexandru Furculita

ฉันชอบอันนี้มากกว่า 6x gzipped
Solo

101

ปัญหานั้นไม่สำคัญเมื่อพูดถึงวัตถุโฮสต์หรือวัตถุชนิดใดก็ตามที่ซับซ้อนกว่าค่าของถุง

  • คุณเรียกใช้ getter เพื่อรับค่าหรือคัดลอกตัวบอกคุณสมบัติหรือไม่
  • เกิดอะไรขึ้นถ้าเป้าหมายผสานมีตัวตั้งค่า (เป็นทรัพย์สินของตัวเองหรือในห่วงโซ่ต้นแบบ) คุณพิจารณาค่าที่มีอยู่แล้วหรือเรียกตัวตั้งค่าเพื่ออัพเดตค่าปัจจุบันหรือไม่?
  • คุณเรียกใช้ฟังก์ชันคุณสมบัติของตัวเองหรือคัดลอกไป จะเกิดอะไรขึ้นถ้าพวกเขาผูกพันฟังก์ชั่นหรือฟังก์ชั่นลูกศรขึ้นอยู่กับบางสิ่งบางอย่างในห่วงโซ่ขอบเขตของพวกเขาในเวลาที่พวกเขากำหนด?
  • เกิดอะไรขึ้นถ้ามันเป็นเหมือนโหนด DOM แน่นอนว่าคุณไม่ต้องการถือมันเป็นวัตถุอย่างง่ายและรวมคุณสมบัติทั้งหมดเข้าไว้ด้วยกัน
  • วิธีการจัดการกับโครงสร้าง "ง่าย" เช่นอาร์เรย์หรือแผนที่หรือชุด? พิจารณาพวกเขาอยู่แล้วหรือรวมพวกเขาด้วยหรือไม่
  • วิธีการจัดการกับคุณสมบัติของตัวเองไม่นับไม่ได้?
  • สิ่งที่เกี่ยวกับ subtrees ใหม่? เพียงกำหนดโดยการอ้างอิงหรือการโคลนแบบลึก?
  • วิธีการจัดการกับวัตถุแช่แข็ง / ปิดผนึก / ไม่สามารถขยายได้?

สิ่งอื่นที่ควรทราบ: กราฟวัตถุที่มีรอบ มักจะไม่ยากที่จะจัดการกับ - เพียงแค่เก็บSetวัตถุต้นฉบับที่เยี่ยมชมแล้ว - แต่มักจะถูกลืม

คุณควรเขียนฟังก์ชันการผสานที่คาดว่าจะมีค่าดั้งเดิมและวัตถุง่าย ๆ เท่านั้น - ประเภทส่วนใหญ่ที่อัลกอริทึมโคลนโครงสร้างสามารถจัดการได้ - เป็นแหล่งที่ผสาน โยนหากพบอะไรก็ตามที่ไม่สามารถจัดการได้หรือเพียงแค่มอบหมายโดยการอ้างอิงแทนที่จะเป็นการผสานแบบลึก

กล่าวอีกนัยหนึ่งไม่มีอัลกอริธึมที่เหมาะกับทุกขนาดคุณต้องม้วนตัวเองหรือมองหาวิธีไลบรารีที่เกิดขึ้นเพื่อครอบคลุมการใช้งานของคุณ


2
แก้ตัวสำหรับ V8 devs ที่จะไม่ใช้การถ่ายโอน "สถานะเอกสาร" ที่ปลอดภัย
neaumusic

คุณยกประเด็นที่ดีมากมายและฉันชอบที่จะเห็นการดำเนินการตามคำแนะนำของคุณ ดังนั้นฉันจึงพยายามทำให้หนึ่งด้านล่าง คุณช่วยดูและแสดงความคิดเห็นได้ไหม? stackoverflow.com/a/48579540/8122487
RaphaMex

66

นี่คือเวอร์ชันที่ไม่เปลี่ยนรูป (ไม่แก้ไขอินพุต) ของคำตอบของ @ Salakar มีประโยชน์หากคุณกำลังทำสิ่งที่ประเภทการเขียนโปรแกรมการทำงาน

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 ดูโพสต์ก่อนหน้าโดยฉันสำหรับฟังก์ชั่น
isObject

อัปเดตมัน หลังจากการทดสอบบางอย่างฉันพบข้อผิดพลาดกับวัตถุที่ซ้อนกันอย่างลึกล้ำ
CpILL

3
มันเป็นชื่อคุณสมบัติที่คำนวณได้ครั้งแรกจะใช้ค่าkeyเป็นชื่อคุณสมบัติในภายหลังจะทำให้ "สำคัญ" ชื่อคุณสมบัติ ดู: es6-features.org/#ComputedPropertyNames
CpILL

2
ในisObjectคุณไม่จำเป็นต้องตรวจสอบ&& item !== nullในตอนท้ายเพราะบรรทัดเริ่มต้นด้วยitem &&ไม่?
ephemer

2
หากแหล่งที่มามีวัตถุย่อยที่อยู่ลึกกว่าเป้าหมายวัตถุเหล่านั้นจะยังคงอ้างอิงค่าเดียวกันในmergedDeepเอาต์พุตของ (ฉันคิดว่า) เช่น 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 นี้เป็นปัญหาหรือไม่ มันไม่ได้กลายพันธุ์อินพุต แต่การกลายพันธุ์ในอนาคตใด ๆ กับอินพุตสามารถกลายพันธุ์เอาต์พุตและในทางกลับกัน w / การกลายพันธุ์กับเอาต์พุตกลายพันธุ์อินพุต สำหรับสิ่งที่มีค่าแม้ว่า ramda ของR.merge()มีพฤติกรรมเดียวกัน
James Conkling

40

เนื่องจากปัญหานี้ยังคงใช้งานต่อไปนี้เป็นวิธีการอื่น:

  • ES6 / 2015
  • ไม่เปลี่ยนรูป (ไม่แก้ไขวัตถุต้นฉบับ)
  • จัดการอาร์เรย์ (เชื่อมต่อพวกเขา)

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


นี่เป็นสิ่งที่ดี อย่างไรก็ตามเมื่อเรามีอาร์เรย์ที่มีองค์ประกอบที่ซ้ำกันจะมีการต่อกัน (มีองค์ประกอบที่ซ้ำกัน) ฉันปรับสิ่งนี้เพื่อรับพารามิเตอร์ (อาร์เรย์ไม่ซ้ำกัน: จริง / เท็จ)
นักบินอวกาศ

1
เพื่อให้อาร์เรย์มีความพิเศษคุณสามารถเปลี่ยน prev[key] = pVal.concat(...oVal);เป็นprev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
Richard Herries

1
สวยและสะอาดมาก !! คำตอบที่ดีที่สุดแน่นอนที่นี่!
538ROMEO

รุ่งโรจน์. อันนี้แสดงให้เห็นว่าอาร์เรย์ได้รับการผสานซึ่งเป็นสิ่งที่ฉันกำลังมองหา
Tschallacka

ใช่วิธีการแก้ปัญหา @CplLL กล่าวว่าไม่เปลี่ยนรูป แต่ใช้ความไม่แน่นอนของวัตถุที่แท้จริงภายในฟังก์ชันในขณะที่ใช้reduce ไม่ได้
Augustin Riedinger

30

ฉันรู้ว่ามีคำตอบมากมายอยู่แล้วและความคิดเห็นมากมายที่โต้แย้งว่าพวกเขาไม่ทำงาน ฉันทามติเพียงอย่างเดียวคือมันซับซ้อนจนไม่มีใครสร้างมาตรฐานให้มันมีความซับซ้อนเพื่อให้ไม่มีใครทำมาตรฐานสำหรับมันอย่างไรก็ตามคำตอบที่ยอมรับส่วนใหญ่ใน SO เปิดเผย "เทคนิคง่ายๆ" ที่ใช้กันอย่างแพร่หลาย ดังนั้นสำหรับพวกเราทุกคนอย่างฉันที่ไม่ใช่ผู้เชี่ยวชาญ แต่ต้องการเขียนโค้ดที่ปลอดภัยยิ่งขึ้นด้วยการเข้าใจถึงความซับซ้อนของจาวาสคริปต์ฉันจะพยายามทำให้กระจ่าง

ก่อนที่จะทำให้มือของเราสกปรกให้ฉันอธิบาย 2 จุด:

  • [การปฏิเสธความรับผิด] ฉันขอเสนอฟังก์ชั่นด้านล่างที่ฝึกฝนวิธีที่เราวนลึกเข้าไปในวัตถุจาวาสคริปต์สำหรับการคัดลอกและแสดงให้เห็นถึงสิ่งที่มักจะแสดงความคิดเห็นโดยเร็วเกินไป มันไม่พร้อมสำหรับการผลิต เพื่อความชัดเจนฉันได้ทิ้งข้อควรพิจารณาอื่น ๆ ไว้เช่นวัตถุทรงกลม (ติดตามโดยชุดสัญลักษณ์หรือคุณสมบัติสัญลักษณ์ที่ไม่มีข้อขัดแย้ง)การคัดลอกค่าอ้างอิงหรือการโคลนแบบลึกวัตถุปลายทางที่ไม่เปลี่ยนรูปแบบ (การโคลนแบบลึกอีกครั้ง?) วัตถุแต่ละประเภทรับ / ตั้งค่าคุณสมบัติผ่านaccessors ... นอกจากนี้ฉันไม่ได้ทดสอบประสิทธิภาพ - มันสำคัญมาก - เพราะมันไม่ใช่จุดที่นี่เช่นกัน
  • ฉันจะใช้สำเนาหรือกำหนดเงื่อนไขการใช้บริการแทนการผสาน เพราะในใจของฉันการรวมกันเป็นแบบอนุรักษ์นิยมและควรล้มเหลวเมื่อเกิดความขัดแย้ง ที่นี่เมื่อเกิดข้อขัดแย้งเราต้องการให้แหล่งที่มาเขียนทับปลายทาง Object.assignไม่ชอบ

คำตอบที่มีfor..inหรือObject.keysทำให้เข้าใจผิด

การทำสำเนาอย่างลึกซึ้งดูเหมือนพื้นฐานและเป็นเรื่องธรรมดาที่เราคาดหวังว่าจะได้พบกับสายการบินเดียวหรืออย่างน้อยก็ชนะอย่างรวดเร็วผ่านการสอบถามซ้ำอย่างง่าย ๆ เราไม่คาดหวังว่าเราจะต้องมีห้องสมุดหรือเขียนฟังก์ชันที่กำหนดเอง 100 บรรทัด

เมื่อฉันอ่านคำตอบของ Salakar เป็นครั้งแรกฉันคิดว่าฉันทำได้ดีกว่าและเรียบง่ายกว่าเดิม (คุณสามารถเปรียบเทียบกับObject.assignเปิดได้x={a:1}, y={a:{b:1}}) จากนั้นฉันก็อ่านคำตอบของ the8472และฉันคิดว่า ... ไม่มีการไปไหนง่าย ๆ การปรับปรุงคำตอบที่ได้รับแล้วจะไม่ทำให้เราไปไกล

ลองทำสำเนาลึก ๆ แล้วทิ้งไว้เฉยๆ เพียงแค่พิจารณาว่าคน (แยก) คุณสมบัติการแยกเพื่อคัดลอกวัตถุที่ง่ายมาก

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.keysจะละเว้นคุณสมบัติที่ไม่สามารถนับได้ของตัวเองคุณสมบัติของคีย์สัญลักษณ์และคุณสมบัติของต้นแบบทั้งหมด มันอาจจะดีถ้าวัตถุของคุณไม่มีสิ่งเหล่านั้น แต่โปรดระลึกไว้เสมอว่าObject.assignจัดการคุณสมบัติที่นับจำนวนด้วยสัญลักษณ์ของตัวเอง ดังนั้นสำเนาที่คุณกำหนดเองจึงสูญเสียไป

for..inจะให้คุณสมบัติของแหล่งที่มาของต้นแบบและห่วงโซ่ต้นแบบเต็มรูปแบบโดยที่คุณไม่ต้องการมัน (หรือรู้ว่ามัน) เป้าหมายของคุณอาจจบลงด้วยคุณสมบัติมากเกินไปผสมคุณสมบัติต้นแบบและคุณสมบัติของตัวเอง

หากคุณกำลังเขียนฟังก์ชั่นที่ใช้งานทั่วไปและคุณไม่ได้ใช้Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbolsหรือObject.getPrototypeOfคุณส่วนใหญ่อาจจะทำมันผิด

สิ่งที่ต้องพิจารณาก่อนเขียนฟังก์ชันของคุณ

ก่อนอื่นให้แน่ใจว่าคุณเข้าใจว่าวัตถุ Javascript คืออะไร ใน Javascript วัตถุทำจากคุณสมบัติของมันเองและวัตถุต้นแบบ (แม่) วัตถุต้นแบบในทางกลับกันทำจากคุณสมบัติของตัวเองและวัตถุต้นแบบ เป็นต้นการกำหนดโซ่ต้นแบบ

สถานที่ให้บริการเป็นคู่ของคีย์ ( stringหรือsymbol) และ descriptor ( valueหรือget/ setaccessor และคุณลักษณะเช่นenumerable)

ในที่สุดก็มีหลายประเภทของวัตถุ คุณอาจต้องการจัดการวัตถุที่แตกต่างจากวันที่วัตถุหรือฟังก์ชั่นวัตถุ

ดังนั้นการเขียนข้อความที่ลึกซึ้งคุณควรตอบคำถามเหล่านั้นอย่างน้อย:

  1. ฉันจะพิจารณาอะไรที่ลึก (เหมาะสำหรับการค้นหาแบบเรียกซ้ำ) หรือแบบแบน
  2. ฉันต้องการคัดลอกคุณสมบัติใด (แจกแจง / ไม่นับไม่ได้, สตริง - คีย์ / สัญลักษณ์ - คีย์, คุณสมบัติของตัวเอง / คุณสมบัติของตัวเอง, ค่า / ตัวอธิบาย ... )

สำหรับตัวอย่างของฉันฉันพิจารณาว่ามีเพียงobject Objects เท่านั้นที่ลึกเพราะวัตถุอื่น ๆ ที่สร้างโดยตัวสร้างอื่นอาจไม่เหมาะสมสำหรับการมองในเชิงลึก ปรับแต่งจากSOนี้

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

และฉันทำoptionsวัตถุเพื่อเลือกสิ่งที่จะคัดลอก (สำหรับการสาธิต)

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

ฟังก์ชั่นที่เสนอ

คุณสามารถทดสอบได้ในเสียงพึมพำนี้

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

ที่สามารถใช้เช่นนี้

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

13

ฉันใช้ lodash:

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

2
โปรดทราบว่าการผสานจะเปลี่ยนวัตถุหากคุณต้องการสิ่งที่ไม่กลายพันธุ์วัตถุดังนั้น _cloneDeep(value1).merge(value2)
geckos

3
@geckos คุณสามารถทำได้ _.merge ({}, value1, value2)
Spenhouet

10

นี่คือการใช้ 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);
};

และการทดสอบหน่วย:

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

9

นี่คือโซลูชัน ES6 อื่นทำงานกับวัตถุและอาร์เรย์

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
นี่คือการทดสอบและ / หรือส่วนหนึ่งของห้องสมุดดูดี แต่อยากจะตรวจสอบให้แน่ใจว่าได้รับการพิสูจน์แล้วบ้าง


8

ฉันต้องการนำเสนอทางเลือก ES5 แบบง่ายๆ ฟังก์ชันรับพารามิเตอร์ 2 ตัว - targetและsourceต้องเป็นประเภท "วัตถุ" Targetจะเป็นวัตถุที่เกิดขึ้น Targetเก็บคุณสมบัติดั้งเดิมไว้ทั้งหมด แต่ค่าอาจเปลี่ยนแปลงได้

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

กรณี:

  • หากtargetไม่มีsourceทรัพย์สินให้targetรับมา
  • หากtargetมีsourceคุณสมบัติและtarget& sourceไม่ใช่ทั้งวัตถุ (3 รายจาก 4) targetคุณสมบัติของจะถูกแทนที่;
  • ถ้าtargetมีsourceคุณสมบัติและทั้งคู่เป็นวัตถุ / อาร์เรย์ (เหลืออีก 1 กรณี) การเรียกซ้ำเกิดขึ้นเมื่อรวมสองวัตถุ (หรือการต่อกันสองอาร์เรย์)

ยังพิจารณาดังต่อไปนี้ :

  1. array + obj = array
  2. obj + array = obj
  3. obj + obj = obj (รวมซ้ำ)
  4. array + array = array (concat)

คาดเดาได้รองรับประเภทดั้งเดิมเช่นเดียวกับอาร์เรย์และวัตถุ นอกจากนี้ในขณะที่เราสามารถรวม 2 วัตถุฉันคิดว่าเราสามารถรวมมากกว่า 2 ผ่านฟังก์ชั่นลด

ลองดูตัวอย่าง (และลองดูด้วยถ้าคุณต้องการ) :

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

มีข้อ จำกัด - ความยาวสแตกการเรียกของเบราว์เซอร์ เบราว์เซอร์สมัยใหม่จะทำให้เกิดข้อผิดพลาดในระดับการเรียกซ้ำที่ลึกมาก (ลองคิดถึงการโทรซ้อนหลายพันสาย) นอกจากนี้คุณมีอิสระที่จะปฏิบัติต่อสถานการณ์เช่นอาร์เรย์ + วัตถุ ฯลฯ ตามที่คุณต้องการโดยการเพิ่มเงื่อนไขใหม่และตรวจสอบประเภท



7

มีวิธีทำเช่นนี้หรือไม่?

หากไลบรารี npmสามารถใช้เป็นวิธีแก้ปัญหาการผสานวัตถุขั้นสูงจากคุณจะช่วยให้สามารถผสานวัตถุอย่างลึกซึ้งและปรับแต่ง / ลบล้างทุกการกระทำผสานเดียวโดยใช้ฟังก์ชันการเรียกกลับที่คุ้นเคย แนวคิดหลักของมันคือมากกว่าแค่การผสานอย่างลึกซึ้ง - จะเกิดอะไรขึ้นกับมูลค่าเมื่อปุ่มสองปุ่มเหมือนกัน ? ไลบรารีนี้จะดูแลสิ่งนั้น - เมื่อคีย์สองตัวปะทะกันobject-merge-advancedชั่งน้ำหนักประเภทโดยมีจุดประสงค์เพื่อเก็บข้อมูลให้มากที่สุดเท่าที่จะเป็นไปได้หลังจากทำการผสาน:

การรวมคีย์ออบเจ็กต์การคีย์ชนิดการชั่งน้ำหนักเพื่อเก็บข้อมูลให้มากที่สุด

คีย์อาร์กิวเมนต์ของอินพุตแรกถูกทำเครื่องหมาย # 1, อาร์กิวเมนต์ที่สองคือ - # 2 ขึ้นอยู่กับแต่ละประเภทจะมีการเลือกหนึ่งรายการสำหรับค่าของผลลัพธ์ ในแผนภาพ "วัตถุ" หมายถึงวัตถุธรรมดา (ไม่ใช่อาร์เรย์ ฯลฯ )

เมื่อคีย์ไม่ขัดแย้งพวกเขาทั้งหมดป้อนผลลัพธ์

จากตัวอย่างข้อมูลของคุณหากคุณใช้object-merge-advancedเพื่อรวมข้อมูลโค้ดของคุณ:

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

อัลกอริทึมมันวนซ้ำคีย์วัตถุอินพุตทั้งหมดเปรียบเทียบและสร้างและส่งคืนผลลัพธ์ที่ผสานใหม่


6

ฟังก์ชั่นต่อไปนี้สร้างสำเนาของวัตถุที่ลึกซึ่งครอบคลุมการทำสำเนาแบบดั้งเดิมอาร์เรย์และวัตถุ

 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

วิธีแก้ปัญหาง่ายๆด้วย ES5 (เขียนทับค่าที่มีอยู่):

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


สิ่งที่ฉันต้องการ - es6 ก่อให้เกิดปัญหาในการสร้าง - ตัวเลือก es5 นี้คือลูกระเบิด
danday74

5

ตัวอย่างส่วนใหญ่ที่นี่ดูเหมือนซับซ้อนเกินไปฉันใช้หนึ่งใน TypeScript ฉันสร้างฉันคิดว่ามันควรจะครอบคลุมกรณีส่วนใหญ่ (ฉันจัดการอาร์เรย์เป็นข้อมูลปกติเพียงแค่แทนที่พวกเขา)

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

สิ่งเดียวกันใน JS ธรรมดาในกรณี:

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

ต่อไปนี้เป็นกรณีทดสอบของฉันเพื่อแสดงวิธีการใช้งาน

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

โปรดแจ้งให้เราทราบหากคุณคิดว่าฉันขาดฟังก์ชั่นบางอย่าง


5

หากคุณต้องการที่จะมีหนึ่งซับโดยไม่ต้องมีขนาดใหญ่ห้องสมุดเช่น lodash ผมขอแนะนำให้คุณใช้deepmerge ( npm install deepmerge)

จากนั้นคุณสามารถทำได้

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

เพื่อรับ

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

สิ่งที่ดีคือมันมาพร้อมกับการพิมพ์สำหรับ TypeScript ทันที นอกจากนี้ยังช่วยให้ผสานอาร์เรย์ วิธีการแก้ปัญหาทุกรอบที่แท้จริงนี้คือ


4

เราสามารถใช้$ .extend (true, object1, object2)สำหรับการผสานอย่างลึกซึ้ง คุณค่าที่แท้จริงหมายถึงการรวมสองวัตถุแบบวนซ้ำการแก้ไขครั้งแรก

$ ขยาย (จริงเป้าหมายวัตถุ)


9
ผู้ถามไม่เคยระบุว่าพวกเขากำลังใช้ jQuery และดูเหมือนจะขอวิธีแก้ปัญหาจาวาสคริปต์แบบดั้งเดิม
Teh JoE

นี่เป็นวิธีที่ง่ายมากในการทำเช่นนี้และได้ผล ทางออกที่ทำงานได้ที่ฉันจะพิจารณาถ้าฉันเป็นคนหนึ่งที่ถามคำถามนี้ :)
kashiraja

นี่เป็นคำตอบที่ดีมาก แต่ไม่มีลิงก์ไปยังซอร์สโค้ดไปยัง jQuery jQuery มีผู้ใช้งานจำนวนมากที่ทำงานในโครงการและพวกเขาใช้เวลาในการทำสำเนาอย่างถูกต้อง นอกจากนี้รหัสที่มาเป็นธรรม "ง่าย": github.com/jquery/jquery/blob/master/src/core.js#L125 "ง่าย" jQuery.isPlainObject()อยู่ในคำพูดเพราะมันเริ่มทำงานซับซ้อนเมื่อขุดลง ซึ่งทำให้เกิดความซับซ้อนในการพิจารณาว่าบางสิ่งบางอย่างเป็นวัตถุธรรมดาหรือไม่ซึ่งคำตอบส่วนใหญ่ที่นี่พลาดโดยการยิงยาว เดาว่าภาษา jQuery เขียนด้วยภาษาอะไร?
CubicleSoft

4

ตรงไปตรงมาวิธีง่ายๆที่ใช้งานได้ดีObject.assignเพียงแค่ทำงานและทำงานกับอาเรย์โดยไม่มีการดัดแปลงใด ๆ

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

ตัวอย่าง

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

ฉันมีปัญหานี้เมื่อโหลดสถานะแคชที่เปลี่ยนใหม่ หากฉันเพิ่งโหลดสถานะแคชฉันจะพบข้อผิดพลาดสำหรับเวอร์ชันแอปใหม่ที่มีโครงสร้างสถานะที่อัปเดต

มันถูกกล่าวถึงแล้วว่า lodash มีmergeฟังก์ชั่นซึ่งฉันใช้:

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

3

คำตอบจำนวนมากใช้โค้ดหลายสิบบรรทัดหรือต้องการเพิ่มไลบรารี่ใหม่ให้กับโปรเจ็กต์ แต่ถ้าคุณใช้การเรียกซ้ำนี่เป็นเพียงโค้ด 4 บรรทัด

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

การจัดการอาร์เรย์: เวอร์ชันด้านบนเขียนทับค่าอาร์เรย์เก่าด้วยค่าใหม่ หากคุณต้องการให้เก็บค่าอาร์เรย์เก่าและเพิ่มค่าใหม่เพียงแค่เพิ่มelse if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])บล็อกข้างบนelsestatament และคุณพร้อมแล้ว


1
ฉันชอบ แต่ต้องการการตรวจสอบที่ไม่ได้กำหนดอย่างง่ายสำหรับ 'ปัจจุบัน' หรืออย่างอื่น {foo: undefined} ไม่ได้ผสาน เพียงแค่เพิ่ม if (ปัจจุบัน) ก่อน for for loop
Andreas Pardeike

ขอบคุณสำหรับคำแนะนำ
Vincent

2

นี่คืออีกหนึ่งฉันเพิ่งเขียนที่รองรับอาร์เรย์ มันเชื่อมโยงพวกเขา

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

ใช้ฟังก์ชั่นนี้:

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 ซึ่งเป็นห้องสมุดที่ดีของฟังก์ชั่นจาวาสคริปต์มี mergeDeepLeft และ mergeDeepRight งานเหล่านี้ทำงานได้ดีสำหรับปัญหานี้ โปรดดูเอกสารที่นี่: https://ramdajs.com/docs/#mergeDeepLeft

สำหรับตัวอย่างที่เป็นปัญหาเราสามารถใช้:

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

ทดสอบหน่วย:

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

ฉันพบโซลูชัน 2 บรรทัดเท่านั้นที่จะได้รับการผสานลึกใน javascript แจ้งให้เราทราบว่าวิธีนี้ได้ผลสำหรับคุณ

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)

วัตถุชั่วคราวจะพิมพ์ {a: {b: 'd', e: 'f', x: 'y'}}


1
สิ่งนี้ไม่ได้ทำการผสานอย่างแท้จริง merge({x:{y:{z:1}}}, {x:{y:{w:2}}})มันจะล้มเหลวด้วย และจะไม่อัปเดตค่าที่มีอยู่ใน obj1 หาก obj2 มีค่าเช่นmerge({x:{y:1}}, {x:{y:2}})กัน
Oreilles

1

บางครั้งคุณไม่จำเป็นต้องผสานลึกถึงแม้ว่าคุณจะคิดเช่นนั้น ตัวอย่างเช่นหากคุณมีการกำหนดค่าเริ่มต้นด้วยวัตถุที่ซ้อนกันและคุณต้องการที่จะขยายมันลึกลงไปด้วยการกำหนดค่าของคุณเองคุณสามารถสร้างชั้นเรียนสำหรับที่ แนวคิดง่ายมาก:

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

คุณสามารถแปลงเป็นฟังก์ชั่น (ไม่ใช่ตัวสร้าง)


1

นี่คือการผสานแบบลึกที่ราคาถูกซึ่งใช้เป็นรหัสเพียงเล็กน้อยเท่าที่ฉันจะนึกได้ แต่ละแหล่งเขียนทับคุณสมบัติก่อนหน้าเมื่อมีอยู่

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

ฉันกำลังใช้ฟังก์ชั่นสั้น ๆ ต่อไปนี้สำหรับการรวมวัตถุลึก
มันใช้งานได้ดีสำหรับฉัน
ผู้เขียนอธิบายวิธีการทำงานที่นี่อย่างสมบูรณ์

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

};

แม้ว่าลิงก์นี้อาจตอบคำถามได้ดีกว่าหากรวมส่วนสำคัญของคำตอบไว้ที่นี่และให้ลิงก์สำหรับการอ้างอิง คำตอบสำหรับลิงค์เท่านั้นอาจไม่ถูกต้องหากหน้าเว็บที่เชื่อมโยงนั้นเปลี่ยนแปลง - จากการรีวิว
Chris Camaratta

สวัสดี @ChrisCamaratta ไม่เพียง แต่เป็นส่วนสำคัญที่นี่ทุกอย่างอยู่ที่นี่ - ฟังก์ชั่นและวิธีการใช้งาน ดังนั้นนี่ไม่ใช่ลิงก์คำตอบเท่านั้น นี่คือฟังก์ชั่นที่ฉันใช้เพื่อรวมวัตถุต่างๆเข้าด้วยกัน ลิงค์นี้ก็ต่อเมื่อคุณต้องการให้ผู้เขียนอธิบายว่ามันทำงานอย่างไร ฉันรู้สึกว่ามันจะเป็นความเสียหายให้กับชุมชนเพื่อพยายามอธิบายการทำงานได้ดีกว่าผู้เขียนที่สอน JavaScript ขอบคุณสำหรับความคิดเห็น
John Shearing

ฮะ. อาจเป็นเพราะฉันพลาดหรือรหัสไม่ปรากฏในอินเทอร์เฟซผู้ตรวจทานเมื่อฉันตรวจทาน ฉันยอมรับว่านี่เป็นคำตอบที่มีคุณภาพ มันจะปรากฏขึ้นผู้ตรวจสอบคนอื่น ๆ เอาชนะการประเมินครั้งแรกของฉันดังนั้นฉันคิดว่าคุณโอเค ขออภัยสำหรับธงแรงบันดาลใจ
Chris Camaratta

ที่ดี! @ChrisCamaratta ขอบคุณที่ช่วยฉันเข้าใจว่าเกิดอะไรขึ้น
John Shearing
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.