ตอนนี้แพลตฟอร์มมาพร้อมกับ structuredClone() ซึ่งเป็นฟังก์ชันในตัวสำหรับการทำสำเนาแบบเจาะลึก
ก่อนหน้านี้ คุณต้องใช้วิธีแก้ปัญหาชั่วคราวและไลบรารีเพื่อสร้างสำเนาเชิงลึกของค่า JavaScript ตอนนี้แพลตฟอร์มมาพร้อมกับ structuredClone()
ซึ่งเป็นฟังก์ชันในตัวสําหรับการคัดลอกระดับลึก
สำเนาระดับตื้น
การคัดลอกค่าใน 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 ประเภท ได้แก่ สตริง ตัวเลข bigint บูลีน ไม่ได้ระบุ สัญลักษณ์ และค่า Null
MDN — Primitive
ระบบจะจัดการค่าที่ไม่ใช่แบบพื้นฐานเป็นการอ้างอิง ซึ่งหมายความว่าการคัดลอกค่านั้นเป็นเพียงการคัดลอกการอ้างอิงไปยังออบเจ็กต์พื้นฐานเดียวกันเท่านั้น ซึ่งส่งผลให้เกิดลักษณะการคัดลอกแบบตื้น
สำเนาที่ลึก
ตรงข้ามกับการคัดลอกแบบตื้นคือการคัดลอกแบบลึก อัลกอริทึมการคัดลอกแบบลึกจะคัดลอกพร็อพเพอร์ตี้ของออบเจ็กต์ทีละรายการด้วย แต่เรียกใช้ตัวเองแบบซ้ำซ้อนเมื่อพบการอ้างอิงถึงออบเจ็กต์อื่น ซึ่งจะสร้างสำเนาของออบเจ็กต์นั้นด้วย ซึ่งอาจมีความสำคัญมากในการตรวจสอบว่าโค้ด 2 รายการไม่ได้แชร์ออบเจ็กต์โดยไม่ได้ตั้งใจและไม่ได้ดัดแปลงสถานะของกันและกันโดยไม่รู้ตัว
ก่อนหน้านี้ยังไม่มีวิธีง่ายๆ หรือสะดวกในการสร้างการคัดลอกค่าแบบเจาะลึกใน JavaScript ผู้ใช้จํานวนมากใช้ไลบรารีของบุคคลที่สาม เช่น ฟังก์ชัน cloneDeep()
ของ Lodash วิธีที่พบบ่อยที่สุดในการแก้ปัญหานี้คือการแฮ็กโดยใช้ JSON
const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));
อันที่จริง การแก้ปัญหานี้ได้รับความนิยมมากจน V8 เพิ่มประสิทธิภาพ JSON.parse()
อย่างจริงจัง โดยเฉพาะรูปแบบข้างต้นเพื่อให้ทำงานได้เร็วที่สุด แม้ว่าจะรวดเร็ว แต่ก็มีจุดอ่อนและข้อควรระวังอยู่ 2-3 ข้อดังนี้
- โครงสร้างข้อมูลที่เรียกซ้ำ:
JSON.stringify()
จะแสดงข้อผิดพลาดเมื่อคุณส่งโครงสร้างข้อมูลที่เรียกซ้ำ กรณีนี้เกิดขึ้นได้ค่อนข้างง่ายเมื่อทำงานกับลิงค์ลิสต์หรือต้นไม้ - ประเภทในตัว:
JSON.stringify()
จะแสดงข้อผิดพลาดหากค่ามี JS ในตัวอื่นๆ เช่นMap
,Set
,Date
,RegExp
หรือArrayBuffer
- ฟังก์ชัน:
JSON.stringify()
จะทิ้งฟังก์ชันโดยไม่มีการแจ้งเตือน
การโคลนแบบมีโครงสร้าง
แพลตฟอร์มจําเป็นต้องสร้างสําเนาค่า JavaScript แบบเจาะลึกในบางตำแหน่งอยู่แล้ว นั่นคือ การจัดเก็บค่า JS ใน IndexedDB ต้องใช้การทําให้เป็นอนุกรมรูปแบบหนึ่งเพื่อให้จัดเก็บไว้ในดิสก์ได้ และทําให้เป็นอนุกรมอีกครั้งในภายหลังเพื่อกู้คืนค่า JS ในทํานองเดียวกัน การส่งข้อความไปยัง WebWorker ผ่าน postMessage()
จะต้องโอนค่า JS จากขอบเขต JS หนึ่งไปยังอีกขอบเขตหนึ่ง อัลกอริทึมที่ใช้ในการดำเนินการนี้เรียกว่า "การโคลนแบบมีโครงสร้าง" ซึ่งนักพัฒนาแอปเข้าถึงได้ยากจนกระทั่งเมื่อไม่นานมานี้
แต่ตอนนี้ได้เปลี่ยนไปแล้ว มีการแก้ไขข้อกำหนด HTML เพื่อแสดงฟังก์ชันชื่อ structuredClone()
ที่ทำงานตามอัลกอริทึมดังกล่าวอย่างตรงที่สุด เพื่อเป็นช่องทางให้นักพัฒนาซอฟต์แวร์สร้างสำเนาเชิงลึกของค่า JavaScript ได้อย่างง่ายดาย
const myDeepCopy = structuredClone(myOriginal);
เท่านี้ก็เรียบร้อย นี่คือ API ทั้งหมด หากต้องการดูรายละเอียดเพิ่มเติม โปรดดูบทความ MDN
ฟีเจอร์และข้อจำกัด
การโคลนแบบมีโครงสร้างช่วยแก้ไขข้อบกพร่องหลายประการ (แต่ไม่ใช่ทั้งหมด) ของเทคนิค JSON.stringify()
การโคลนแบบมีโครงสร้างสามารถจัดการโครงสร้างข้อมูลที่วนซ้ำ รองรับประเภทข้อมูลในตัวจํานวนมาก และโดยทั่วไปจะมีประสิทธิภาพมากขึ้นและมักจะเร็วกว่า
อย่างไรก็ตาม ฟีเจอร์นี้ยังมีข้อจำกัดบางอย่างที่อาจทำให้คุณไม่ทันตั้งตัว ดังนี้
- โปรโตไทป์: หากใช้
structuredClone()
กับอินสแตนซ์ของคลาส คุณจะได้รับออบเจ็กต์ธรรมดาเป็นค่าที่แสดงผล เนื่องจากการโคลนแบบมีโครงสร้างจะทิ้งเชนโปรโตไทป์ของออบเจ็กต์ - ฟังก์ชัน: หากออบเจ็กต์มีฟังก์ชัน
structuredClone()
จะแสดงข้อยกเว้นDataCloneError
- ไม่สามารถทำซ้ำได้: ค่าบางค่าไม่ใช่ Structured Data ที่สามารถทำซ้ำได้ โดยเฉพาะอย่างยิ่ง
Error
และโหนด DOM ซึ่งจะทําให้structuredClone()
แสดงข้อผิดพลาด
หากข้อจำกัดเหล่านี้เป็นข้อจำกัดที่ทำให้คุณไม่สามารถใช้กรณีการใช้งานได้ ไลบรารีอย่าง Lodash ยังคงมีการใช้งานที่กำหนดเองสำหรับอัลกอริทึมการโคลนจากข้อมูลเชิงลึกอื่นๆ ที่อาจหรือไม่เหมาะกับกรณีการใช้งานของคุณ
ประสิทธิภาพ
แม้ว่าเราจะไม่ได้ทำการเปรียบเทียบการทดสอบประสิทธิภาพแบบละเอียดใหม่ แต่เราได้ทำการเปรียบเทียบเมื่อต้นปี 2018 ก่อนที่จะเปิดตัว structuredClone()
สมัยนั้น JSON.parse()
เป็นตัวเลือกที่เร็วที่สุดสำหรับวัตถุขนาดเล็กมาก เราคาดว่าจะยังคงเหมือนเดิม เทคนิคที่อาศัยการโคลนแบบมีโครงสร้างทำงานได้เร็วกว่า (อย่างมาก) สำหรับวัตถุขนาดใหญ่ เนื่องจาก structuredClone()
เวอร์ชันใหม่ไม่มีค่าใช้จ่ายเพิ่มเติมจากการละเมิด API อื่นๆ และมีประสิทธิภาพมากกว่า JSON.parse()
เราจึงขอแนะนำให้คุณใช้ structuredClone()
เป็นแนวทางเริ่มต้นในการสร้างสำเนาโดยละเอียด
บทสรุป
หากต้องการสร้างการคัดลอกค่าใน JS แบบเจาะลึก ซึ่งอาจเป็นเพราะคุณใช้โครงสร้างข้อมูลที่แก้ไขไม่ได้ หรือต้องการตรวจสอบว่าฟังก์ชันสามารถจัดการออบเจ็กต์โดยไม่ส่งผลต่อออบเจ็กต์ต้นฉบับ คุณไม่จำเป็นต้องใช้วิธีแก้ปัญหาชั่วคราวหรือไลบรารีอีกต่อไป ตอนนี้ระบบนิเวศ JS มี structuredClone()
เยี่ยมไปเลย