กลับไปหน้าบทความ
#JavaScript#Structured Clone#AbortController#Event Delegation#Memory Leak

3 เทคนิค JavaScript ที่ช่วยให้โค้ดเร็ว ลื่น และลดโอกาสรั่วหน่วยความจำ

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

27 มกราคม 2569อ่านประมาณ 2 นาที

แชร์บทความ

3 เทคนิค JavaScript ที่ช่วยให้โค้ดเร็ว ลื่น และลดโอกาสรั่วหน่วยความจำ

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 ซับซ้อนมากกว่าเดิม เช่น

  • Date
  • Map
  • Set
  • ArrayBuffer
  • TypedArray
  • ข้อมูลที่มีการอ้างอิงวนกลับ

เหมาะกับงานแบบไหน

เทคนิคนี้เหมาะมากเมื่อคุณต้องการคัดลอก 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

หากฝึกใช้ทั้งสามอย่างจนเป็นนิสัย คุณจะได้โค้ดที่เร็วขึ้น นิ่งขึ้น และดูเป็นมืออาชีพมากขึ้นอย่างเห็นได้ชัด