เรียนรู้ว่า Web Workers และ Service Workers คืออะไร แตกต่างกันอย่างไร และเมื่อใดควรใช้แต่ละแบบเพื่อให้หน้าเร็วขึ้น งานเบื้องหลัง การแคช และการรองรับออฟไลน์

เบราว์เซอร์รัน JavaScript ส่วนใหญ่ของคุณบน เธรดหลัก (main thread)—ที่เดียวกันกับที่จัดการอินพุตผู้ใช้, แอนิเมชัน และการวาดหน้า เมื่อมีงานหนักเกิดขึ้นที่นั่น (การแยกวิเคราะห์ข้อมูลขนาดใหญ่, ประมวลผลภาพ, การคำนวณซับซ้อน) UI อาจสะดุดหรือ "ค้าง" ได้ Workers ถูกออกแบบมาเพื่อย้ายงานบางอย่าง ออกจากเธรดหลัก หรือ ออกจากการควบคุมโดยตรงของหน้า เพื่อให้แอปของคุณยังตอบสนองได้
ถ้าหน้าของคุณกำลังยุ่งกับการคำนวณ 200ms เบราว์เซอร์จะไม่สามารถเลื่อนหน้าได้อย่างราบรื่น ตอบสนองการคลิก หรือรักษาแอนิเมชันที่ 60fps ได้ Workers ช่วยโดยการให้คุณทำงานเบื้องหลัง ขณะที่เธรดหลักยังมุ่งไปที่อินเทอร์เฟซ
A Web Worker คือเธรด JavaScript เบื้องหลังที่คุณสร้างจากหน้า เหมาะกับงานที่หนักด้าน CPU ที่ถ้าไปรันบนเธรดหลักจะบล็อก UI
A Service Worker เป็น worker พิเศษที่อยู่ระหว่างเว็บแอปของคุณกับเครือข่าย มันสามารถดักคำขอ แคชการตอบกลับ และเปิดใช้งานฟีเจอร์เช่นการรองรับออฟไลน์และการแจ้งเตือนแบบ push
คิดว่า Web Worker เป็นผู้ช่วยที่ทำงานคำนวณในห้องอื่น คุณส่งข้อความไปให้ มันทำงาน แล้วส่งกลับมา
คิดว่า Service Worker เป็นผู้คัดกรองหน้าประตูด้านหน้า คำขอสำหรับหน้า สคริปต์ และ API จะผ่านมัน และมันสามารถตัดสินใจว่าจะดึงจากเครือข่าย เสิร์ฟจากแคช หรือตอบในแบบกำหนดเอง
By the end, you’ll know:
postMessage) เข้ากับโมเดล worker และทำไม Cache Storage API ถึงสำคัญสำหรับออฟไลน์บทนำนี้อธิบาย "ทำไม" และโมเดลความคิด—ต่อไปเราจะลงรายละเอียดว่าทำงานแต่ละประเภทเป็นอย่างไรและเหมาะกับโปรเจกต์จริงอย่างไร
เมื่อคุณเปิดเว็บเพจ ส่วนใหญ่ที่คุณ "รู้สึก" ว่าเกิดขึ้นจะอยู่บน เธรดหลัก มันรับผิดชอบการวาดพิกเซล (rendering), ตอบสนองการแตะและคลิก (input) และรัน JavaScript จำนวนมาก
เพราะการเรนเดอร์ การจัดการอินพุต และ JavaScript มักสลับกันใช้เธรดเดียวกัน งานหนึ่งงานช้าอาจทำให้ทุกอย่างอื่นต้องรอ นั่นคือสาเหตุที่ปัญหาประสิทธิภาพมักแสดงออกมาในรูปแบบของปัญหาการตอบสนอง ไม่ใช่แค่ "โค้ดช้า"
ความรู้สึกเมื่อมีการบล็อกสำหรับผู้ใช้:
JavaScript มี API แบบอะซิงโครนัสมากมาย—เช่น fetch(), timers, events—ซึ่งช่วยให้คุณหลีกเลี่ยงการรออย่างเฉยๆ แต่ async ไม่ได้ทำให้งานหนักรันพร้อมกับการเรนเดอร์โดยอัตโนมัติ
หากคุณทำการคำนวณหนัก (การประมวลผลภาพ, การ crunch JSON ขนาดใหญ่, crypto, การกรองซับซ้อน) บนเธรดหลัก มันยังแข่งกับการอัปเดต UI อยู่ "Async" อาจเลื่อน เวลา ที่มันรัน แต่ยังอาจรันบนเธรดหลักและทำให้เกิด jank เมื่อมันประมวลผล
Workers มีเพื่อให้เบราว์เซอร์สามารถรักษาการตอบสนองของหน้าได้ ในขณะที่ยังทำงานสำคัญในเบื้องหลัง
สรุป: workers คือวิธีปกป้องเธรดหลัก เพื่อให้แอปของคุณยังโต้ตอบได้ขณะมีการทำงานในเบื้องหลังจริง
A Web Worker คือวิธีรัน JavaScript นอกเธรดหลัก แทนที่จะไปแข่งกับงาน UI (การเรนเดอร์ การเลื่อน ตอบคลิก) worker จะรันบนเธรดพื้นหลังของตัวเอง งานหนักจึงเสร็จโดยไม่ทำให้หน้ารู้สึก "ติด"
คิดว่าเป็น: หน้าโฟกัสกับการโต้ตอบผู้ใช้ ขณะที่ worker จัดการงานหนักด้าน CPU อย่างการแยกวิเคราะห์ไฟล์ขนาดใหญ่ การคำนวณตัวเลข หรือเตรียมข้อมูลสำหรับกราฟ
Web Worker รันใน เธรดแยกพร้อม global scope ของตัวเอง มันยังเข้าถึง API เว็บบางอย่างได้ (timers, fetch ในเบราว์เซอร์หลายตัว, crypto เป็นต้น) แต่ถูกแยกจากหน้าจงใจ
มีรูปแบบที่พบบ่อย:
ถ้าคุณไม่เคยใช้ workers มาก่อน ตัวอย่างส่วนใหญ่ที่เห็นเป็น dedicated workers
Workers ไม่สามารถเรียกฟังก์ชันในหน้าโดยตรง การสื่อสารทำโดยส่งข้อความ:
postMessage()postMessage() เช่นกันสำหรับข้อมูลไบนารีขนาดใหญ่ คุณมักปรับปรุงประสิทธิภาพด้วยการโอนกรรมสิทธิ์ของ ArrayBuffer (เพื่อไม่ให้คัดลอก) ซึ่งช่วยให้การส่งข้อความเร็ว
เพราะ worker ถูกแยก มีข้อจำกัดสำคัญบางอย่าง:
window หรือ document worker รันภายใต้ self (global scope ของ worker) และ API ที่มีให้จะแตกต่างจากหน้าหลักเมื่อใช้อย่างถูกต้อง Web Worker เป็นวิธีง่ายๆ ในการปรับปรุง ประสิทธิภาพเธรดหลัก โดยไม่ต้องเปลี่ยนพฤติกรรมของแอป—แค่ย้ายที่ให้การทำงานหนักเกิดขึ้น
Web Workers เหมาะเมื่อหน้าของคุณรู้สึก "ติด" เพราะ JavaScript รันงานหนักบนเธรดหลัก เธรดหลักยังรับผิดชอบอินเทอร์แอ็คชันผู้ใช้และการเรนเดอร์ ดังนั้นงานหนักตรงนั้นสามารถทำให้เกิด jank, คลิกหน่วง และการเลื่อนค้างได้
ใช้ Web Worker เมื่อคุณมีงานหนักด้าน CPU ที่ไม่จำเป็นต้องเข้าถึง DOM โดยตรง:
ตัวอย่างปฏิบัติ: หากคุณได้รับ payload JSON ขนาดใหญ่และการแยกวิเคราะห์ทำให้ UI กระตุก ให้ย้ายการแยกวิเคราะห์ไปยัง worker แล้วส่งผลกลับ
การสื่อสารกับ worker เกิดผ่าน postMessage สำหรับข้อมูลไบนารีขนาดใหญ่ ให้ใช้ transferable objects (เช่น ArrayBuffer) เพื่อให้เบราว์เซอร์โอนกรรมสิทธิ์หน่วยความจำแทนการคัดลอก:
// main thread
worker.postMessage(buffer, [buffer]); // transfers the ArrayBuffer
สิ่งนี้มีประโยชน์สำหรับบัฟเฟอร์เสียง ไบต์ภาพ หรือข้อมูลชิ้นใหญ่ๆ
Workers มีค่าใช้จ่าย: ไฟล์เพิ่มเติม การส่งข้อความ และการดีบักที่ต่างออกไป หลีกเลี่ยงเมื่อ:
ถ้างานสามารถทำให้เกิดการหยุดสังเกตได้ (มัก ~50ms+) และสามารถระบุเป็น "input → compute → output" โดยไม่ต้องเข้าถึง DOM มักคุ้มค่าที่จะใช้ Web Worker หากงานส่วนใหญ่เป็นการอัปเดต UI ให้อยู่บนเธรดหลักและปรับแต่งตรงนั้นแทน
A Service Worker คือไฟล์ JavaScript พิเศษที่รันในพื้นหลังของเบราว์เซอร์และทำงานเหมือน เลเยอร์เครือข่ายที่โปรแกรมได้ สำหรับไซต์ของคุณ แทนที่จะรันบนหน้า มันอยู่ระหว่างเว็บแอปของคุณกับเครือข่าย ให้คุณตัดสินใจว่าเมื่อแอปร้องขอทรัพยากร (HTML, CSS, API, รูปภาพ) จะทำอย่างไร
Service Worker มีวงจรชีวิตแยกจากแท็บใดแท็บหนึ่ง:
เพราะมันอาจถูกหยุดและเริ่มใหม่ได้ตลอดเวลา ให้มองมันเป็นสคริปต์ขับเคลื่อนด้วยเหตุการณ์: ทำงานให้เร็ว เก็บสถานะใน storage ที่ถาวร และอย่าสมมติว่ามันจะทำงานตลอดเวลา
Service Workers ถูกจำกัดให้อยู่ ภายใน origin เดียวกัน (domain/protocol/port เดียวกัน) และควบคุมหน้าภายใต้ scope ของมัน—โดยปกติตำแหน่งโฟลเดอร์ที่ไฟล์ worker ถูกเสิร์ฟ (และด้านล่าง) นอกจากนี้ต้องใช้ HTTPS (ยกเว้น localhost) เพราะมันสามารถกระทบคำขอเครือข่ายได้
Service Worker ถูกใช้เป็นหลักเพื่อยืนอยู่ระหว่างเว็บแอปของคุณกับเครือข่าย มันสามารถตัดสินใจว่าเมื่อใดจะใช้เครือข่าย ใช้ข้อมูลจากแคช และเมื่อใดจะทำงานเบื้องหลัง—โดยไม่บล็อกหน้า
งานที่พบบ่อยที่สุดคือเปิดใช้งานประสบการณ์ออฟไลน์หรือการใช้งานเมื่อการเชื่อมต่อไม่ดีโดยการแคชแอสเซ็ตและการตอบกลับ
กลยุทธ์แคชที่ใช้กันบ่อย:
นี่มักทำโดยใช้ Cache Storage API และการจัดการเหตุการณ์ fetch
Service Workers ช่วยให้ความรู้สึกเร็วขึ้นเมื่อกลับมาเยือนโดย:
ผลคือคำขอเครือข่ายลดลง การเริ่มต้นเร็วขึ้น และประสิทธิภาพสม่ำเสมอบนการเชื่อมต่อที่ไม่เสถียร
Service Workers สามารถขับเคลื่อนฟีเจอร์เบื้องหลังเช่น push notifications และ background sync (การรองรับแตกต่างกันไปตามเบราว์เซอร์และแพลตฟอร์ม) นั่นหมายความว่าคุณสามารถแจ้งผู้ใช้หรือพยายามส่งคำขอที่ล้มเหลวอีกครั้งในภายหลัง แม้หน้าไม่ได้เปิดอยู่
ถ้าคุณกำลังสร้าง progressive web app Service Workers เป็นส่วนสำคัญเบื้องหลัง:
ถ้าจำได้เพียงอย่างเดียว: Web Workers ช่วยให้หน้าทำงานหนักได้โดยไม่ทำให้ UI ค้าง ในขณะที่ Service Workers ช่วยให้แอปควบคุมคำขอเครือข่ายและทำงานเหมือนแอปที่ติดตั้งได้ (PWA)
A Web Worker เหมาะกับ งานหนักด้าน CPU—การแยกวิเคราะห์ข้อมูลขนาดใหญ่ สร้าง thumbnails คำนวณ—เพื่อให้เธรดหลักยังตอบสนอง
A Service Worker เหมาะกับ การจัดการคำขอและงานวงจรชีวิตของแอป—การแคชแบบออฟไลน์ การซิงค์เบื้องหลัง การแจ้งเตือน push มันสามารถยืนอยู่ระหว่างแอปกับเครือข่าย
A Web Worker มัก ผูกกับหน้า/แท็บ เมื่อหน้าปิด worker มักจะถูกยุติ (ยกเว้นกรณีพิเศษเช่น SharedWorker)
A Service Worker เป็น event-driven เบราว์เซอร์สามารถเริ่มมันเพื่อจัดการเหตุการณ์ (เช่น fetch หรือ push) แล้วหยุดเมื่อว่าง นั่นหมายความว่ามันสามารถทำงานแม้จะไม่มีแท็บเปิด
A Web Worker ไม่สามารถดักคำขอเครือข่ายของหน้าได้ มันสามารถ fetch() ข้อมูลด้วยตัวเอง แต่ไม่สามารถเขียนทับ แคช หรือเสิร์ฟการตอบสำหรับส่วนอื่นของไซต์ได้
A Service Worker สามารถดักคำขอเครือข่าย (ผ่านเหตุการณ์ fetch) ตัดสินใจว่าจะไปเครือข่าย ดึงจากแคช หรือคืน fallback
A Web Worker ไม่จัดการ HTTP caching ให้กับแอปของคุณ
A Service Worker มักใช้ Cache Storage API เพื่อเก็บและเสิร์ฟคู่ request/response—ซึ่งเป็นพื้นฐานสำหรับการแคชแบบออฟไลน์และการโหลดซ้ำที่ "ทันที"
การรัน worker ส่วนใหญ่เกี่ยวกับ ตำแหน่งที่มันรัน และ วิธีโหลด Web Workers ถูกสร้างโดยสคริปต์หน้าโดยตรง Service Workers ถูกติดตั้งโดยเบราว์เซอร์และยืนอยู่ "หน้าประตู" ของคำขอเครือข่ายสำหรับไซต์ของคุณ
Web Worker เริ่มเมื่อหน้าของคุณสร้างมัน คุณชี้ไปที่ไฟล์ JavaScript แยกต่างหาก แล้วสื่อสารผ่าน postMessage:
// main.js (running on the page)
const worker = new Worker('/workers/resize-worker.js', { type: 'module' });
worker.postMessage({ action: 'start', payload: { /* ... */ } });
worker.onmessage = (event) => {
console.log('From worker:', event.data);
};
โมเดลความคิดที่ดี: ไฟล์ worker เป็นสคริปต์อีกตัวที่เพจของคุณสามารถดึงมาได้ แต่จะรันนอกเธรดหลัก
Service Workers ต้องถูกลงทะเบียนจากหน้าที่ผู้ใช้เยี่ยมชม:
// main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
หลังการลงทะเบียน เบราว์เซอร์จะจัดการวงจร install/activate ให้ ไฟล์ sw.js ของคุณสามารถฟังเหตุการณ์เช่น install, activate, และ fetch
Service Workers สามารถดักคำขอเครือข่ายและแคชการตอบกลับได้ หากการลงทะเบียนอนุญาตผ่าน HTTP ผู้โจมตีบนเครือข่ายอาจแทนที่ sw.js ด้วยโค้ดที่เป็นอันตรายและควบคุมการเข้าชมในอนาคตได้ HTTPS (หรือ http://localhost สำหรับการพัฒนา) ปกป้องสคริปต์และทราฟฟิกที่มันส่งผลต่อ
เบราว์เซอร์แคชและอัปเดต worker ต่างจากสคริปต์หน้าโดยปกติ วางแผนการอัปเดต:
sw.js/บันเดิล worker ใหม่)ถ้าต้องการกลยุทธ์การเปิดตัวที่ราบรื่นขึ้น ดู /blog/debugging-workers สำหรับแนวทางการทดสอบที่จับ edge cases ของการอัปเดตได้เร็ว
Workers ล้มเหลวต่างจาก JavaScript ปกติ: พวกมันรันในคอนเท็กซ์แยก มีคอนโซลของตัวเอง และอาจถูกรีสตาร์ทโดยเบราว์เซอร์ วิธีการดีบักที่ดีจะช่วยประหยัดเวลาได้มาก
เปิด DevTools และหา targets ของ worker ใน Chrome/Edge มักจะเห็น worker ใน Sources (หรือผ่านรายการ "Dedicated worker") และในตัวเลือก context ของ Console
ใช้เครื่องมือเดียวกับที่ใช้บนเธรดหลัก:
onmessage handlers และฟังก์ชันที่รันนานถ้าข้อความดูเหมือน "หาย" ให้ตรวจทั้งสองฝั่ง: ยืนยันว่าคุณเรียก worker.postMessage(...), worker มี self.onmessage = ..., และรูปแบบข้อความตรงกัน
Service Workers ดีบักได้ดีที่สุดในแผง Application:
นอกจากนี้ให้ดู Console สำหรับข้อผิดพลาด install/activate/fetch—มักอธิบายได้ว่าทำไมการแคชหรือการทำงานออฟไลน์ไม่ทำงาน
ปัญหาการแคชเป็นตัวดูดเวลาที่ใหญ่ที่สุด: แคชไฟล์ผิดหรือแคชแน่นเกินไปอาจเก็บ HTML/JS เก่าไว้ ในการทดสอบลองทำ hard reload และยืนยันว่าอะไรถูกเสิร์ฟจริง
สำหรับการทดสอบที่สมจริง ใช้ DevTools เพื่อ:
ถ้าคุณวนเวียนพัฒนา PWA การสร้างแอปฐานที่สะอาด (มี Service Worker และเอาต์พุตที่คาดเดาได้) แล้วค่อยปรับกลยุทธ์แคช จะช่วยได้ แพลตฟอร์มอย่าง Koder.ai อาจเป็นประโยชน์สำหรับการทดลอง: คุณสามารถโปรโตไทป์แอป React จาก prompt ในแชท ส่งออกซอร์ส แล้วปรับการตั้งค่า worker และนโยบายแคชด้วยวง Feedback ที่กระชับขึ้น
ใช้ Web Worker เมื่อคุณมีงานหนักด้าน CPU ที่สามารถอธิบายเป็น input → compute → output และไม่ต้องการเข้าถึง DOM โดยตรง。
งานที่เหมาะได้แก่ การแยกวิเคราะห์/แปลง payload ขนาดใหญ่ การบีบอัด/ถอดรหัส การเข้ารหัสลับ การประมวลผลภาพ/เสียง และการกรองข้อมูลที่ซับซ้อน หากงานส่วนใหญ่เป็นการอัปเดต UI หรืออ่าน/เขียน DOM บ่อยๆ worker จะช่วยไม่ได้ (และก็ไม่สามารถเข้าถึง DOM ได้อยู่แล้ว)
ใช้ Service Worker เมื่อคุณต้องการ ควบคุมเครือข่าย: การรองรับแบบออฟไลน์ นโยบายการแคช การโหลดที่เร็วขึ้นในการกลับมา เยื้องคำขอ (request routing) และ (เมื่อรองรับ) push หรือ background sync。
ถ้าปัญหาของคุณคือ “UI ค้างเพราะต้องคำนวณ” นั่นคือกรณีของ Web Worker ถ้าปัญหาคือ “การโหลดช้าหรือออฟไลน์ใช้งานไม่ได้” นั่นคือกรณีของ Service Worker
ไม่จำเป็นทั้งคู่ Web Workers และ Service Workers เป็นฟีเจอร์ที่แยกจากกัน。
คุณสามารถใช้แค่ตัวใดตัวหนึ่งหรือใช้ทั้งสองร่วมกันเมื่อแอปต้องการทั้งการคำนวณและคุณสมบัติด้านเครือข่าย/ออฟไลน์
ส่วนใหญ่เป็นเรื่องของ ขอบเขตและอายุการทำงาน。
fetch) แม้ไม่มีแท็บเปิดอยู่ แล้วปิดตัวเมื่อว่างไม่สามารถ Web Workers ไม่มี window/document และจึงไม่สามารถอ่านหรือแก้ไข DOM ได้。
ถ้าต้องการอัปเดต UI ส่งข้อมูลกลับมาที่เธรดหลักผ่าน postMessage() แล้วให้โค้ดในเพจอัปเดต DOM ต่อ
ไม่สามารถ Service Workers ไม่มีสิทธิ์เข้าถึง DOM ด้วยเหตุผลเดียวกันกับ Web Worker: มันทำงานแยกจากเพจและอาจจะทำงานเมื่อไม่มีเอกสารใดๆ เปิดอยู่。
หากต้องการมีผลต่อสิ่งที่ผู้ใช้เห็น ให้สื่อสารกับเพจที่ถูกควบคุมผ่าน messaging (เช่น Clients API + postMessage()), แล้วให้เพจอัปเดต UI
ใช้ postMessage() ทั้งสองฝั่ง。
worker.postMessage(data)self.postMessage(result)สำหรับข้อมูลไบนารีขนาดใหญ่ ให้ใช้ transferables เช่น ArrayBuffer เพื่อลดการคัดลอก:
Service Worker ยืนอยู่ระหว่างแอปของคุณกับเครือข่ายและตอบคำขอได้โดยใช้ Cache Storage API。
กลยุทธ์ทั่วไป:
ควรเลือกกลยุทธ์ตามชนิดของทรัพยากร (app shell vs API data) แทนการใช้กฎเดียวทั้งหมด
ได้—แต่แยกความรับผิดชอบให้ชัดเจน。
แบบแผนที่พบบ่อย:
การแยกหน้าที่จะช่วยหลีกเลี่ยงการรวมตรรกะ UI เข้าไปในบริบทเบื้องหลังและทำให้ประสิทธิภาพคาดเดาได้
ใช้ DevTools ที่เหมาะสมสำหรับแต่ละชนิด:
onmessage, และโปรไฟล์เพื่อตรวจว่าเธรดหลักยังตอบสนองขณะที่ worker ทำงานเมื่อแก้บั๊กการแคช ให้ยืนยันเสมอว่าอะไรถูกเสิร์ฟจริง (จากเน็ตเวิร์กหรือจากแคช) และทดสอบในโหมดออฟไลน์/การจำลองเครือข่าย
worker.postMessage(buffer, [buffer]);