3 เทคนิค JavaScript ที่ช่วยให้โค้ดเร็ว ลื่น และลดโอกาสรั่วหน่วยความจำ
Structured Clone, AbortController และ Event Delegation คือ 3 เทคนิคสำคัญที่ช่วยให้จัดการข้อมูล งาน async และ event ได้อย่างมีประสิทธิภาพมากขึ้น หากใช้ร่วมกันอย่างถูกจุด จะช่วยให้ UI ตอบสนองไวขึ้น โค้ดอ่านง่ายขึ้น และลดบั

3 เทคนิค JavaScript ที่ช่วยให้โค้ดเร็ว ลื่น และลดโอกาสรั่วหน่วยความจำ
นักพัฒนา JavaScript จำนวนมากมักโฟกัสที่การทำให้ฟีเจอร์ใช้งานได้ก่อน แต่เมื่อโปรเจกต์เริ่มซับซ้อนขึ้น ปัญหาที่ตามมาคือโค้ดช้า หน่วง ดูแลยาก หรือเกิดอาการหน่วยความจำค้างโดยไม่รู้ตัว
มี 3 เทคนิคที่ช่วยยกระดับคุณภาพโค้ดได้อย่างชัดเจน ได้แก่ Structured Clone, AbortController และ Event Delegation ซึ่งแต่ละอย่างอาจดูเป็นเรื่องเล็ก แต่เมื่อนำมาใช้ร่วมกัน จะช่วยให้แอปทำงานได้ลื่นขึ้น ลดบั๊ก และจัดการ lifecycle ของหน้าเว็บได้ดีขึ้นมาก
1) Structured Clone: คัดลอกข้อมูลได้ปลอดภัยกว่าเดิม
เวลาต้องทำสำเนา object หลายคนอาจคุ้นกับวิธีอย่าง JSON.parse(JSON.stringify(x)) เพราะเขียนง่ายและใช้ได้ในหลายกรณี แต่ข้อจำกัดของวิธีนี้มีอยู่มาก เช่น
- ข้อมูลประเภท
Dateอาจไม่คงรูปแบบเดิม MapและSetใช้งานไม่ได้ตามที่คาดBigIntอาจเกิดปัญหา- ข้อมูลที่มี circular reference จะพังทันที
structuredClone() เป็นวิธีคัดลอกข้อมูลสมัยใหม่ที่รองรับข้อมูลได้หลากหลายกว่า และเหมาะกับงานที่มี state ซับซ้อนมากกว่าเดิม เช่น
DateMapSetArrayBufferTypedArray- ข้อมูลที่มีการอ้างอิงวนกลับ
เหมาะกับงานแบบไหน
เทคนิคนี้เหมาะมากเมื่อคุณต้องการคัดลอก state ที่ซ้อนหลายชั้น เพื่อแก้ไขเฉพาะบางส่วนโดยไม่กระทบต้นฉบับ เช่น
- ฟอร์มขนาดใหญ่
- ระบบ undo/redo
- การทำ snapshot ก่อนส่งข้อมูลไป API
- การประมวลผลข้อมูลที่ต้องแยกเวอร์ชันก่อนแก้ไข
ข้อควรระวัง
แม้ structuredClone() จะสะดวก แต่ก็ไม่ได้รองรับทุกอย่าง เช่น
- function
- DOM node
- class instance บางประเภท
ถ้าเจอกรณีเหล่านี้ แนวทางที่ดีคือแยก data layer ออกจาก behavior layer ให้ชัดเจน กล่าวคือ clone เฉพาะข้อมูล และเก็บพฤติกรรมหรือเมธอดไว้คนละส่วน จะช่วยให้โค้ดสะอาดและดูแลง่ายขึ้น
2) AbortController: ยกเลิกงาน async ให้เป็นนิสัย
หนึ่งในปัญหาคลาสสิกของงานฝั่ง frontend คือ request ซ้อนกันโดยไม่จำเป็น เช่น ผู้ใช้พิมพ์ช่องค้นหาเร็วมากจนมีการยิง request หลายครั้งติดกัน แล้วผลลัพธ์เก่ากลับมาช้ากว่าแต่ดันไปทับผลลัพธ์ใหม่
อีกกรณีที่พบบ่อยคือ ผู้ใช้ออกจากหน้าไปแล้ว แต่ fetch ยังทำงานต่อ ทำให้เปลืองทรัพยากร และในบางสถานการณ์อาจนำไปสู่อาการคล้าย memory leak
AbortController จึงเป็นเครื่องมือสำคัญที่ช่วยให้เราสามารถสั่งยกเลิกงาน async ได้อย่างเป็นระบบ
แนวทางใช้งานที่ควรทำ
- สร้าง controller แยกตามงาน หรือแยกตาม lifecycle
- เมื่อมีงานใหม่เข้ามา ให้ยกเลิกงานเก่าที่ไม่จำเป็น
- เมื่อ component ถูกถอดออก หรือหน้าถูกปิด ควรยกเลิกงานที่ค้างอยู่ทันที
- ใช้ร่วมกับงานประเภทค้นหา, auto-complete, โหลดข้อมูล, modal หรือหน้าที่มีการเข้าออกบ่อย
เทคนิคที่มีประโยชน์มาก
อีกแนวคิดที่หลายคนยังใช้ไม่เต็มที่ คือการรวม resource ที่เกี่ยวข้องกับหน้าหนึ่งไว้ภายใต้การควบคุมเดียวกัน เช่น
- fetch
- listener
- interval
- งาน async อื่น ๆ
เมื่อถึงเวลาปิดหน้า หรือ teardown component ก็สามารถยกเลิกทุกอย่างได้ในจังหวะเดียว วิธีคิดแบบนี้ช่วยลดบั๊กประเภท “ลืมถอด listener” หรือ “มีงานค้างหลังปิดหน้า” ได้อย่างมาก
3) Event Delegation: ลด listener ให้เหลือน้อยที่สุด
หากหน้าเว็บมีปุ่มหรือรายการจำนวนมาก การผูก addEventListener ให้ทุก element อาจทำให้จัดการยาก และเพิ่มภาระโดยไม่จำเป็น โดยเฉพาะเมื่อมี element ที่ถูกสร้างขึ้นแบบ dynamic ภายหลัง
Event Delegation คือการติด listener ไว้ที่ container หลักเพียงตัวเดียว จากนั้นใช้ event ที่ bubble ขึ้นมาเพื่อตรวจสอบว่าเหตุการณ์เกิดจาก element ไหน
ข้อดีของวิธีนี้
- ลดจำนวน listener ลงอย่างมาก
- รองรับ element ที่สร้างทีหลังได้ทันที
- รวม logic การจัดการ event ไว้จุดเดียว
- ทำให้โค้ดอ่านง่ายและดูแลง่ายขึ้น
รูปแบบที่แนะนำ
แนวทางที่ใช้งานได้ดีคือการกำหนด data-action หรือ data-id ไว้บน element เช่นปุ่มหรือรายการ แล้วใน handler ใช้ e.target.closest(...) เพื่อหา element ที่เกี่ยวข้อง วิธีนี้ช่วยให้โค้ดมีโครงสร้างที่ชัดเจน และต่อยอดได้ง่ายเมื่อระบบใหญ่ขึ้น
เมื่อใช้ทั้ง 3 เทคนิคร่วมกัน จะเกิดอะไรขึ้น
จุดเด่นของทั้งสามเทคนิคไม่ใช่แค่ประโยชน์รายตัว แต่คือการเสริมกันเป็นระบบ
- Event Delegation ช่วยลดภาระจาก listener จำนวนมาก
- AbortController ช่วยควบคุม lifecycle ของงาน async และยกเลิกสิ่งที่ไม่จำเป็นได้ทันเวลา
- Structured Clone ช่วยให้จัดการ state ได้ปลอดภัย ลด side effects ที่คาดเดายาก
เมื่อรวมกันแล้ว คุณจะได้โค้ดที่
- ตอบสนองไวขึ้น
- ลดโอกาสเกิดบั๊กจากข้อมูลถูกแก้ไขผิดจุด
- ลดงานค้างที่ไม่จำเป็น
- จัดการหน้าเว็บหรือ component ได้เป็นระบบมากขึ้น
- ลดความเสี่ยงของปัญหาหน่วยความจำสะสม
เช็กลิสต์สำหรับนำไปใช้ทันที
หากอยากเริ่มใช้งานจริง ลองใช้รายการตรวจสอบต่อไปนี้
- ใช้
structuredClone()เมื่อต้องคัดลอกข้อมูลซับซ้อนก่อนแก้ไข - ให้ทุกงาน
fetch, search หรือ auto-complete มีAbortControllerเสมอ - หากมีปุ่มหรือรายการจำนวนมาก ให้ใช้ event delegation ที่ container
- ตั้งชื่อ function และ
data-actionให้สื่อความหมาย เพื่อให้ทั้งทีมอ่านโค้ดได้เร็ว
สัญญาณเตือนว่าแอปอาจเริ่มมีปัญหาหน่วยความจำ
บางครั้ง memory leak ไม่ได้แสดงออกอย่างชัดเจนตั้งแต่แรก แต่จะค่อย ๆ สะสมจนเริ่มเห็นอาการ เช่น
- เปิดและปิดหน้าซ้ำ ๆ แล้ว RAM เพิ่มขึ้นเรื่อย ๆ
- มี listener หรือ interval ที่ยังค้างอยู่
- มี request เก่ากลับมาทับผลลัพธ์ใหม่
- หน้าเว็บเริ่มหน่วงทั้งที่ logic หลักไม่ได้ซับซ้อนมาก
หากพบอาการเหล่านี้ การกลับมาทบทวน 3 เทคนิคนี้มักช่วยแก้ปัญหาได้ตรงจุด
สรุป
การเขียน JavaScript ที่ดีไม่ได้มีแค่ทำให้ฟีเจอร์ทำงานได้ แต่ต้องทำให้โค้ดปลอดภัย ดูแลง่าย และไม่สร้างภาระสะสมในระยะยาวด้วย
หัวใจสำคัญของแนวคิดนี้คือ
- Clone ให้ถูก ด้วย
structuredClone() - ยกเลิกให้เป็น ด้วย
AbortController - ฟัง event ให้น้อย ด้วย
Event Delegation
หากฝึกใช้ทั้งสามอย่างจนเป็นนิสัย คุณจะได้โค้ดที่เร็วขึ้น นิ่งขึ้น และดูเป็นมืออาชีพมากขึ้นอย่างเห็นได้ชัด