JavaScript ES2025 มาพร้อมเมธอด Set 7 ตัว เลิกเขียน intersection · union ด้วยมือได้แล้ว
ES2025 เพิ่มเมธอด Set สำเร็จรูปให้ JavaScript ครบ 7 ตัว ทั้ง union · intersection · difference และตัวเช็กเซตย่อย เปลี่ยนโค้ดหาสมาชิกร่วมที่เคยยาวสามบรรทัดให้เหลือบรรทัดเดียว พร้อมใช้งานได้ทันทีบนเบราว์เซอร์และ Node เวอร์ชันช่วงหลังโดยไม่ต้องลง polyfill

โค้ดหาส่วนร่วมด้วยมืออย่าง new Set([...a].filter(x => b.has(x))) เป็นรูปแบบที่คนเขียน JavaScript คุ้นเคยกันดี แต่ตอนนี้เราไม่ต้องเขียนแบบนั้นอีกแล้ว เพราะมาตรฐาน ES2025 เพิ่มเมธอด Set สำเร็จรูปมาให้ถึง 7 ตัว ซึ่งตั้งชื่อตามหน้าที่ตรงๆ จนอ่านแล้วเข้าใจได้ทันที อันที่จริง Set ซึ่งเป็นโครงสร้างข้อมูลแบบไม่ซ้ำ อยู่คู่กับ JavaScript มาตั้งแต่ยุค ES6 หรือราว 10 ปีก่อน แต่ยุคนั้นยังไม่มีเมธอดสำเร็จรูปสำหรับจับข้อมูลสองชุดมาหาส่วนร่วมหรือรวมกัน เวลาใช้งานจริงเราจึงต้องเขียนวนเองด้วย spread ร่วมกับ filter มาโดยตลอด
ES2025 เข้ามาเปลี่ยนเรื่องนี้ทั้งหมด เมธอดใหม่ทั้ง 7 ตัวแบ่งออกเป็นสองกลุ่มชัดเจน คือกลุ่มที่คำนวณแล้วส่งคืน Set ชุดใหม่ออกมา กับกลุ่มที่คืนค่าแค่ true หรือ false ที่สำคัญคือความสามารถเหล่านี้พร้อมใช้งานทันที ไม่ใช่เรื่องของอนาคต เพราะทั้งเบราว์เซอร์และ Node.js รุ่นที่ออกในช่วงปีที่ผ่านมาต่างรองรับครบถ้วน โดยไม่ต้องติดตั้ง polyfill หรือโค้ดเสริมสำหรับเติมฟีเจอร์ให้เบราว์เซอร์รุ่นเก่าให้ยุ่งยาก
สามบรรทัดที่เราเขียนกันจนชิน

เวลาต้องหาส่วนร่วม หาส่วนต่าง หรือรวมข้อมูลสองชุดเข้าด้วยกัน โค้ดที่คุ้นตาจะประมาณนี้
const intersection = new Set([...setA].filter(x => setB.has(x)));
const difference = new Set([...setA].filter(x => !setB.has(x)));
const union = new Set([...setA, ...setB]);
แม้โค้ดรูปแบบนี้จะทำงานได้ถูกต้อง แต่ปัญหาคือเราต้องตั้งใจอ่านโค้ดทั้งบรรทัดก่อนถึงจะเข้าใจเจตนาที่แท้จริง อย่างบรรทัดแรก โค้ดบอกเพียงขั้นตอนว่าเป็นการกระจาย setA ออกมาเป็น array แล้วกรองเอาเฉพาะสมาชิกที่มีใน setB แต่ไม่ได้บอกชัดเจนตั้งแต่แรกว่ากำลังหา intersection พอระบบซับซ้อนขึ้นและมีโค้ดลักษณะนี้กระจายอยู่ตามจุดต่างๆ การกลับมาอ่านแต่ละครั้งจึงต้องเสียเวลาตีความใหม่เสมอ
เมธอด Set ชุดใหม่นี้เป็นส่วนหนึ่งของความสามารถที่ JavaScript ทยอยอัปเดตเข้ามาในช่วงหลัง จนทำให้หลายงานที่เคยต้องพึ่งพาไลบรารีภายนอกอย่าง lodash ตอนนี้เขียนได้ในภาษาเลย โดยไม่ต้องลงแพ็กเกจเสริมอะไรเพิ่มอีก
เมธอดพีชคณิตของเซต 4 ตัวที่คืนออกมาเป็น Set ใหม่

กลุ่มแรกมี 4 เมธอด ทุกตัวจะรับ Set อีกชุดเข้ามาคำนวณ แล้วสร้างเป็น Set ชุดใหม่ส่งกลับมา ลองดูตัวอย่างการทำงานผ่านชุดตัวเลขง่ายๆ ดังนี้
const a = new Set([1, 2, 3]);
const b = new Set([3, 4, 5]);
a.union(b); // Set {1, 2, 3, 4, 5}
a.intersection(b); // Set {3}
a.difference(b); // Set {1, 2}
b.difference(a); // Set {4, 5}
a.symmetricDifference(b); // Set {1, 2, 4, 5}
เราเข้าใจการทำงานได้ตรงๆ จากชื่อเมธอด โดย union จะรวมสมาชิกของทั้งสองชุดเข้าด้วยกัน intersection จะดึงเฉพาะตัวที่มีอยู่ร่วมกัน ซึ่งในตัวอย่างนี้คือเลข 3 ส่วน difference จะคัดเอาเฉพาะตัวที่อยู่ใน a แต่ไม่อยู่ใน b โดยมีข้อควรระวังคือ difference คำนึงถึงทิศทางด้วย การเขียน a.difference(b) จะได้ {1, 2} ในขณะที่ b.difference(a) จะได้ {4, 5} ซึ่งให้ผลลัพธ์ต่างกันเมื่อสลับตำแหน่ง และสุดท้าย symmetricDifference จะเลือกเฉพาะตัวที่โผล่อยู่ในชุดเดียว โดยไม่นับตัวที่ทั้งสองชุดมีเหมือนกัน นั่นคือเอาทุกตัวยกเว้นเลข 3
อีก 3 ตัวที่ตอบแค่จริงหรือเท็จ
เมธอดกลุ่มที่สองจะไม่ส่งคืน Set ก้อนใหม่ แต่จะตอบกลับมาเป็น true หรือ false ให้เรานำไปใช้ในเงื่อนไข if ได้ทันที
new Set([1, 2]).isSubsetOf(new Set([1, 2, 3])); // true
new Set([1, 4]).isSubsetOf(new Set([1, 2, 3])); // false
new Set([1, 2, 3]).isSupersetOf(new Set([1, 2])); // true
new Set([1, 2]).isDisjointFrom(new Set([3, 4])); // true
new Set([1, 2]).isDisjointFrom(new Set([2, 3])); // false
เริ่มจาก isSubsetOf ใช้ตรวจสอบว่าสมาชิกทุกตัวของชุดนี้อยู่ในอีกชุดครบหรือไม่ ซึ่ง {1, 2} ถือเป็นเซตย่อยของ {1, 2, 3} จึงคืนค่า true แต่ {1, 4} จะได้ false เพราะเลข 4 ไม่อยู่ในชุดหลัง ถัดมาคือ isSupersetOf ที่เช็กกลับด้านว่าชุดนี้ครอบคลุมอีกชุดทั้งหมดหรือไม่ และสุดท้าย isDisjointFrom ใช้ถามว่าสองชุดนี้แยกขาดจากกัน ไม่มีตัวไหนซ้ำกันเลยใช่ไหม ซึ่ง {1, 2} กับ {3, 4} ไม่มีเลขตัวใดตรงกันเลยจึงได้ true แต่เมื่อเทียบ {1, 2} กับ {2, 3} ที่มีเลข 2 ซ้ำกัน ก็จะคืนค่าเป็น false
ไม่แก้ของเดิม และส่ง Map เข้าไปก็ได้
มีพฤติกรรมสำคัญ 2 อย่างที่ควรรู้ก่อนนำไปใช้งาน เรื่องแรกคือเมธอดกลุ่มคำนวณทั้ง 4 ตัวจะสร้างและส่งคืน Set ชุดใหม่เสมอ โดยไม่แก้ไขข้อมูลในชุดตั้งต้นหรือชุดที่ส่งเข้าไป เช่น การเรียก a.union(b) จะได้ Set ก้อนใหม่กลับมา โดยที่ a และ b ยังคงมีค่าเดิมไม่เปลี่ยนแปลง เราจึงนำไปเรียกซ้อนกันเป็นทอดๆ ได้อย่างปลอดภัยโดยไม่ต้องกังวลว่าข้อมูลเดิมจะโดนแก้ไข
เรื่องที่สองคืออาร์กิวเมนต์ที่ส่งเข้าไปไม่ต้องเป็น Set ตัวจริงก็ได้ เพราะมาตรฐานเปิดกว้างให้ใช้โครงสร้างแบบ set-like ได้ ขอเพียงเป็นออบเจกต์ที่มีคุณสมบัติครบ 3 อย่าง คือมีค่า size ที่บอกจำนวนสมาชิกเป็นตัวเลข มีเมธอด has สำหรับเช็กว่ามีค่านั้นหรือไม่ และมีเมธอด keys ที่คืนค่าตัวไล่อ่านข้อมูลทีละตัวหรือ iterator ออกมา
ผลพลอยได้ที่น่าสนใจคือ Map ซึ่งเป็นโครงสร้างข้อมูลที่เก็บเป็นคู่ key กับ value มีคุณสมบัติเข้าเงื่อนไขนี้พอดี เราจึงส่ง Map เข้าไปเป็นอาร์กิวเมนต์ได้ทันที โดยเมธอดจะดึงเฉพาะส่วน key ของ Map มาใช้ในการคำนวณ
const activeUserIds = new Map([['u1', userA], ['u2', userB]]);
const bannedIds = new Set(['u2', 'u3']);
bannedIds.intersection(activeUserIds); // Set {'u2'}
โครงสร้างข้อมูลที่เราสร้างขึ้นเองก็เช่นเดียวกัน หากมี size, has และ keys ครบตามข้อกำหนด ก็นำไปใช้งานร่วมกับเมธอดของ Set ได้ทันทีโดยไม่ต้องเสียเวลาแปลงเป็น Set ก่อน
เอาไปเทียบสิทธิ์และเทียบเวอร์ชันเอกสาร
เมื่อเข้าใจชื่อและหน้าที่ของเมธอดแล้ว ตัวอย่างการใช้งานจริงจะช่วยให้เห็นภาพชัดขึ้นว่าจะนำไปปรับใช้ในโค้ดอย่างไร กรณีแรกที่พบได้บ่อยคือการตรวจสอบสิทธิ์การใช้งาน หากเรามีชุดสิทธิ์ที่ผู้ใช้มีอยู่เทียบกับชุดสิทธิ์ที่ระบบต้องการ เมธอด difference จะช่วยบอกได้ทันทีว่ายังขาดสิทธิ์รายการใดบ้าง ในขณะที่ isSubsetOf จะช่วยตอบเป็น true หรือ false ว่าสิทธิ์ที่มีอยู่นั้นเพียงพอให้ผ่านการตรวจสอบหรือไม่
const currentPermissions = new Set(['read', 'write', 'admin']);
const requiredPermissions = new Set(['read', 'write', 'delete']);
const missing = requiredPermissions.difference(currentPermissions);
// Set {'delete'} คือสิทธิ์ที่งานต้องใช้แต่ผู้ใช้ยังไม่มี
const hasAll = requiredPermissions.isSubsetOf(currentPermissions);
// false จึงปฏิเสธการเข้าถึง
อีกหนึ่งกรณีใช้งานคือการเปรียบเทียบความเปลี่ยนแปลงของแท็กเอกสารระหว่างสองเวอร์ชัน การใช้ difference ทั้งสองทิศทางจะช่วยบอกได้ว่ามีแท็กไหนเพิ่มเข้ามาใหม่และแท็กไหนโดนลบออกไป ส่วน intersection จะช่วยบอกว่าแท็กไหนบ้างที่ยังคงอยู่เหมือนเดิม
const before = new Set(['react', 'typescript', 'css']);
const after = new Set(['react', 'vue', 'css', 'tailwind']);
const added = after.difference(before); // Set {'vue', 'tailwind'} อันที่เพิ่มมา
const removed = before.difference(after); // Set {'typescript'} อันที่หายไป
const stable = before.intersection(after); // Set {'react', 'css'} อันที่ยังอยู่
ประโยชน์ที่แท้จริงของการเปลี่ยนมาใช้เมธอดสำเร็จรูป ไม่ใช่แค่ทำให้โค้ดสั้นลงเท่านั้น แต่เป็นการแสดงเจตนาของโค้ดอย่างตรงไปตรงมา ในขณะที่การใช้ spread ร่วมกับ filter เป็นเพียงการบอกขั้นตอนวิธีทำ ซึ่ง Parsa Jiravand นักพัฒนาผู้เขียนบทความต้นทางสรุปไว้ว่า difference() คือการตั้งชื่อตามจุดประสงค์ของงาน ส่วนการสั่ง spread แล้ว filter เป็นเพียงการอธิบายวิธีลงมือทำ การใช้เมธอดใหม่จึงช่วยให้คนที่มาอ่านโค้ดต่อเข้าใจเจตนาได้ทันทีโดยไม่ต้องเสียเวลาแปลความหมายในหัว
เบราว์เซอร์ · Node · TypeScript รองรับหรือยัง
คำถามสำคัญก่อนนำไปใช้งานจริงคือของพวกนี้รองรับในแต่ละที่แล้วแค่ไหน ซึ่งคำตอบคือเมธอดทั้ง 7 ตัวนี้จัดอยู่ในกลุ่ม Baseline 2025 อันเป็นสถานะยืนยันว่าเบราว์เซอร์หลักทุกตัวรองรับครบถ้วนแล้ว โดยเริ่มตั้งแต่ Chrome 122 · Firefox 127 · Safari 17.4 รวมถึง Node.js 22 เป็นต้นไป ดังนั้นเบราว์เซอร์ที่ปล่อยออกมาในช่วงปีที่ผ่านมาจึงเรียกใช้งานได้ทันที โดยไม่ต้องติดตั้ง polyfill หรือเพิ่มขั้นตอนในการ build โค้ดเพิ่มเติม
หากจำเป็นต้องรองรับสภาพแวดล้อมรุ่นเก่ากว่านั้น เราค่อยนำ shim หรือโค้ดเสริมขนาดเล็กสำหรับเติมเมธอดให้ระบบมาใส่เพิ่ม ซึ่งบทความต้นทางระบุว่าเขียนเพิ่มเพียงไม่กี่สิบบรรทัดเท่านั้น พร้อมเน้นย้ำว่าเบราว์เซอร์หลักที่ใช้งานกันในปัจจุบันต่างมีเมธอดเหล่านี้ติดตั้งมาให้ในตัวเรียบร้อยแล้ว
ส่วนในฝั่ง TypeScript ต้องเป็นเวอร์ชัน 5.5 หรือใหม่กว่า จึงจะมี type signature ของเมธอดเหล่านี้มาให้ โดยเมธอดกลุ่มคำนวณจะคืนค่าชนิด Set<T> ขณะที่กลุ่มที่ตอบจริงเท็จจะคืนค่าเป็น boolean ตรงตามประเภทข้อมูลที่ถูกต้อง โดยที่เราไม่ต้องระบุ cast ชนิดข้อมูลเอง
{ "compilerOptions": { "lib": ["DOM", "ES2025"] } }เราทดลองใช้งานได้ทันทีในวันนี้ โดยลองเปิดโปรเจกต์ที่ทำอยู่ แล้วค้นหาคำว่า filter(x => otherSet.has(x)) หรือ new Set([...a, ...b]) ดู ทุกตำแหน่งที่เจอคือเมธอด Set ที่กำลังเขียนด้วยมือ ซึ่งเปลี่ยนมาใช้ตัวสำเร็จรูปแทนได้ทีละจุด และหากกำลังปรับปรุงโค้ดเก่าอยู่ ยังมีท่าเขียน JavaScript อีกสองสามอย่างที่ควรเปลี่ยนมาใช้ของใหม่ในปีนี้ รอให้ลองไล่เก็บต่อได้อีก
การอัปเดตที่ช่วยให้เราลบโค้ดส่วนเกินออกได้ โดยไม่ต้องเพิ่ม dependency หรือเพิ่มขั้นตอนในการ build โค้ดใหม่แบบนี้ ถือเป็นเรื่องที่ไม่ค่อยได้เจอกันบ่อยนัก
ที่มา: บทความ You've been doing Set math by hand. JavaScript finally shipped .union(), .intersection(), and friends. จาก Parsa Jiravand บน dev.to
ชอบเรื่องแนวนี้ มีอีบุ๊คฟรีให้อ่านต่อ
NotebookLM ฉบับเข้าใจง่าย โยนเอกสารให้ AI อ่าน แล้วได้สรุป พอดแคสต์ และคลังความรู้ส่วนตัว
กดสมัครแล้วเราจะส่งเทคนิค AI และของแจกใหม่ๆ ให้ทางอีเมล เลิกรับได้ตลอด
Claude Cowork · The Business Playbook

ฉบับภาษาไทย 15 บท เรียนรู้ผ่านโปรเจกต์จำลองต่อเนื่องทั้งเล่ม ตั้งแต่ตั้งค่า Workspace จัดการไฟล์ เชื่อมแอป ตั้งระบบอัตโนมัติ จนถึงสร้าง Plugin


