ขณะนี้แพลตฟอร์มจัดส่งพร้อมกับstructuredClon() ซึ่งเป็นฟังก์ชันในตัวสำหรับการคัดลอกอย่างละเอียด
ก่อนหน้านี้ คุณต้องใช้วิธีแก้ปัญหาชั่วคราวและไลบรารีเพื่อสร้างสำเนาเชิงลึกของค่า 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
ระบบจะจัดการค่าที่ไม่ใช่พื้นฐานเป็นการอ้างอิง ซึ่งหมายความว่าการคัดลอกค่าเป็นเพียงการคัดลอกการอ้างอิงไปยังออบเจ็กต์เดียวกันเท่านั้น ซึ่งส่งผลให้การคัดลอกมีลักษณะการทำงานในระดับต่ำ
สำเนาเชิงลึก
ส่วนที่ตรงกันข้ามกับสำเนาแบบตื้นๆ ก็คือการคัดลอกแบบ Deep อัลกอริทึมการคัดลอกแบบลึกจะคัดลอกพร็อพเพอร์ตี้ของออบเจ็กต์ทีละรายการด้วย แต่เรียกใช้ตัวเองแบบซ้ำซ้อนเมื่อพบการอ้างอิงถึงออบเจ็กต์อื่น ซึ่งจะสร้างสำเนาของออบเจ็กต์นั้นด้วย ซึ่งอาจมีความสำคัญมากในการตรวจสอบว่าโค้ด 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
- ไม่สามารถโคลนได้: ค่าบางค่าไม่สามารถโคลนแบบมีโครงสร้างได้ ที่สำคัญคือ
Error
และโหนด DOM ซึ่งจะทําให้structuredClone()
แสดงข้อผิดพลาด
หากข้อจำกัดใดๆ เหล่านี้เป็นตัวทำลายข้อตกลงสำหรับกรณีการใช้งานของคุณ ไลบรารีอย่าง Lodash จะยังมีการใช้งานอัลกอริทึมการโคลนเชิงลึกอื่นๆ ที่กำหนดเองซึ่งอาจเหมาะกับกรณีการใช้งานของคุณหรือไม่ก็ได้
ประสิทธิภาพ
แม้ว่าฉันจะยังไม่ได้ทำการเปรียบเทียบแบบไมโครใหม่ แต่ฉันทำการเปรียบเทียบเมื่อต้นปี 2018 ก่อนจะมีการเปิดเผย structuredClone()
สมัยนั้น JSON.parse()
เป็นตัวเลือกที่เร็วที่สุดสำหรับวัตถุขนาดเล็กมาก เราคาดว่าจะยังคงเหมือนเดิม เทคนิคที่อาศัยการโคลนแบบมีโครงสร้างทำงานได้เร็วกว่า (อย่างมาก) สำหรับวัตถุขนาดใหญ่ เนื่องจาก structuredClone()
เวอร์ชันใหม่ไม่มีค่าใช้จ่ายเพิ่มเติมจากการละเมิด API อื่นๆ และมีประสิทธิภาพมากกว่า JSON.parse()
เราจึงขอแนะนำให้คุณใช้ structuredClone()
เป็นแนวทางเริ่มต้นในการสร้างสำเนาโดยละเอียด
บทสรุป
หากต้องการสร้างการคัดลอกค่าใน JS แบบเจาะลึก ซึ่งอาจเป็นเพราะคุณใช้โครงสร้างข้อมูลที่แก้ไขไม่ได้ หรือต้องการตรวจสอบว่าฟังก์ชันสามารถดัดแปลงออบเจ็กต์โดยไม่ส่งผลต่อออบเจ็กต์ต้นฉบับ คุณไม่จำเป็นต้องใช้วิธีแก้ปัญหาชั่วคราวหรือไลบรารีอีกต่อไป ตอนนี้ระบบนิเวศ JS มี structuredClone()
เยี่ยมไปเลย