การคัดลอกเนื้อหาใน JavaScript โดยใช้ StructuredClone

ตอนนี้แพลตฟอร์มจะจัดส่งผลิตภัณฑ์ด้วย StructuredClone() ซึ่งเป็นฟังก์ชันในตัวสำหรับการทำสำเนาแบบเจาะลึก

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

การสนับสนุนเบราว์เซอร์

  • 98
  • 98
  • 94
  • 15.4

แหล่งที่มา

สำเนาตื้น

การคัดลอกค่าใน JavaScript จะเป็นแบบตื้นเกือบทุกครั้ง ซึ่งต่างจากการคัดลอกระดับลึก ซึ่งหมายความว่าการเปลี่ยนแปลงค่าที่ฝังลึกไว้จะปรากฏในสำเนาเช่นเดียวกับต้นฉบับ

วิธีหนึ่งในการสร้างสำเนาแบบสั้นใน JavaScript โดยใช้โอเปอเรเตอร์การกระจายออบเจ็กต์ ... ได้แก่

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

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

myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`

อย่างไรก็ตาม การเพิ่มหรือเปลี่ยนแปลงพร็อพเพอร์ตี้ที่ฝังอยู่ลึกจะส่งผลต่อทั้งสำเนาและข้อมูลต้นฉบับ ดังนี้

myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp) 
// ^ logs `a new value`

นิพจน์ {...myOriginal} จะทำซ้ำในคุณสมบัติ (แจกแจงได้) ของ myOriginal โดยใช้โอเปอเรเตอร์การแพร่กระจาย พร็อพเพอร์ตี้นี้จะใช้ชื่อและค่าของพร็อพเพอร์ตี้ และกำหนดให้กับออบเจ็กต์เปล่าที่สร้างขึ้นใหม่ทีละรายการ ดังนั้น วัตถุที่ได้จะมีรูปร่างเหมือนกัน แต่มีสำเนาของรายการคุณสมบัติและค่าเป็นของตัวเอง ระบบจะคัดลอกค่าดังกล่าวด้วย แต่ค่า JavaScript ดังกล่าวจะได้รับการจัดการต่างจากค่าที่ไม่ใช่ค่าพื้นฐาน วิธีอ้างอิง MDN

ใน JavaScript ค่าพื้นฐาน (ค่าพื้นฐาน, ประเภทข้อมูลเบื้องต้น) คือข้อมูลที่ไม่ใช่ออบเจ็กต์และไม่มีเมธอด ประเภทข้อมูลพื้นฐานมีอยู่ 7 ประเภท ได้แก่ สตริง ตัวเลข บิ้นต์ บูลีน ไม่ระบุ สัญลักษณ์ และ Null

MDN - พื้นฐาน

ค่าที่ไม่ใช่ค่าพื้นฐานจะได้รับการจัดการเป็นreferences ซึ่งหมายความว่าการคัดลอกค่าเป็นเพียงการคัดลอกการอ้างอิงไปยังออบเจ็กต์พื้นฐานเดียวกันเท่านั้น ซึ่งส่งผลให้การคัดลอกเป็นแบบตื้น

สำเนาแบบ Deep

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

ก่อนหน้านี้ ยังไม่มีวิธีที่ง่ายหรือดีในการสร้างสำเนาค่าใน JavaScript ผู้ใช้จำนวนมากใช้ไลบรารีของบุคคลที่สาม เช่น ฟังก์ชัน cloneDeep() ของ Lodash วิธีแก้ปัญหาที่ใช้กันมากที่สุดสำหรับปัญหานี้คือการแฮ็กที่ใช้ JSON ดังนี้

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));

อันที่จริง วิธีนี้เป็นวิธีแก้ปัญหาชั่วคราวที่นิยมใช้กัน กล่าวคือ V8 เพิ่มประสิทธิภาพในเชิงรุก JSON.parse() และโดยเฉพาะรูปแบบข้างต้นเพื่อทำให้รวดเร็วที่สุด และแม้ว่าจะรวดเร็ว แต่ก็มาพร้อมจุดบกพร่องและ 3 แบบ ได้แก่

  • โครงสร้างข้อมูลที่เกิดซ้ำ: JSON.stringify() จะแสดงเมื่อคุณกำหนดโครงสร้างข้อมูลที่เกิดซ้ำ กรณีนี้อาจเกิดขึ้นค่อนข้างง่ายเมื่อใช้รายการหรือโครงสร้างที่เชื่อมโยง
  • ประเภทในตัว: JSON.stringify() จะโยนหากค่ามี JS ในตัวอื่นๆ เช่น Map, Set, Date, RegExp หรือ ArrayBuffer
  • ฟังก์ชัน: JSON.stringify() จะทิ้งฟังก์ชันอย่างเงียบๆ

การโคลนที่มีโครงสร้าง

แพลตฟอร์มนี้ต้องการความสามารถในการสร้างสำเนาแบบลึกของค่า JavaScript ใน 2 ตำแหน่งอยู่แล้ว การจัดเก็บค่า JS ใน IndexedDB ต้องใช้รูปแบบการเรียงอันดับบางอย่างเพื่อให้จัดเก็บไว้ในดิสก์ได้และทำการดีซีเรียลไลซ์เพื่อกู้คืนค่า JS ในภายหลัง ในทำนองเดียวกัน การส่งข้อความไปยัง WebWorker ผ่าน postMessage() จะต้องมีการโอนค่า JS จากขอบเขต JS หนึ่งไปยังขอบเขตอื่น อัลกอริทึมที่ใช้สำหรับขั้นตอนนี้เรียกว่า "การโคลนที่มีโครงสร้าง" และก่อนหน้านี้นักพัฒนาแอปดังกล่าวยังเข้าถึงได้ยากเมื่อเร็วๆ นี้

แต่ตอนนี้เปลี่ยนไปแล้ว มีการแก้ไขข้อกำหนดของ HTML เพื่อแสดงฟังก์ชัน structuredClone() ซึ่งเรียกใช้อัลกอริทึมดังกล่าวเพื่อให้นักพัฒนาซอฟต์แวร์สามารถสร้างสำเนาค่า JavaScript แบบเจาะลึกได้อย่างง่ายดาย

const myDeepCopy = structuredClone(myOriginal);

เท่านี้ก็เรียบร้อย ทั้งหมดนี้คือ API หากต้องการเจาะลึกลงไปในรายละเอียด โปรดอ่านบทความ MDN

ฟีเจอร์และข้อจำกัด

การโคลนที่มีโครงสร้างจะช่วยแก้ปัญหาจุดบกพร่องของเทคนิค JSON.stringify() จำนวนมาก (แต่ไม่ใช่ทั้งหมด) การโคลนที่มีโครงสร้างสามารถจัดการโครงสร้างข้อมูลแบบวนซ้ำ รองรับข้อมูลในตัวหลายประเภท และโดยทั่วไปมีประสิทธิภาพมากกว่าและมักจะทำงานได้เร็วกว่า

แต่ก็มีข้อจำกัดบางประการที่ทำให้คุณเข้าใจผิด เช่น

  • ต้นแบบ: หากใช้ structuredClone() กับอินสแตนซ์คลาส คุณจะได้รับออบเจ็กต์แบบธรรมดาเป็นค่าที่แสดงผล เนื่องจากการโคลนที่มีโครงสร้างจะยกเลิกห่วงโซ่ต้นแบบของออบเจ็กต์
  • ฟังก์ชัน: หากออบเจ็กต์มีฟังก์ชัน structuredClone() จะส่งข้อยกเว้น DataCloneError
  • โคลนไม่ได้: ค่าบางค่าไม่สามารถโคลนที่มีโครงสร้างได้ โดยเฉพาะ Error และโหนด DOM เพราะจะทำให้โยน structuredClone()

หากข้อจำกัดข้อใดข้อหนึ่งเหล่านี้เป็นตัวทำลายดีลสำหรับกรณีการใช้งานของคุณ ไลบรารีอย่าง Lodash จะยังคงมีการใช้งานที่กำหนดเองของอัลกอริทึมการโคลนในรายละเอียดอื่นๆ ซึ่งอาจหรือไม่เหมาะสมกับกรณีการใช้งานของคุณ

การแสดง

แม้จะยังไม่ได้ทำการเปรียบเทียบเกณฑ์เปรียบเทียบย่อยใหม่ แต่ฉันทำการเปรียบเทียบในช่วงต้นปี 2018 ก่อนที่ structuredClone() จะแสดง สมัยนั้น JSON.parse() เป็นตัวเลือกที่เร็วที่สุดสำหรับวัตถุขนาดเล็กมาก เราคาดว่าจะยังคงเหมือนเดิม เทคนิคที่อาศัยการโคลนที่มีโครงสร้างคือ (สำคัญมาก) สำหรับวัตถุขนาดใหญ่ เนื่องจาก structuredClone() ใหม่ไม่มีค่าใช้จ่ายในการละเมิด API อื่นๆ และมีความปลอดภัยมากกว่า JSON.parse() เราจึงขอแนะนำให้คุณกำหนดเป็นค่าเริ่มต้นสำหรับการสร้างสำเนาแบบเจาะลึก

บทสรุป

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