تعرّف على ما هما Web Workers و Service Workers، كيف يختلفان، ومتى تستخدم كل منهما لتحسين سرعة الصفحات، مهام الخلفية، التخزين المؤقت، ودعم عدم الاتصال.

متصفحات الويب تشغل معظم كود جافا سكريبت الخاص بك على الخيط الرئيسي—نفس المكان الذي يتعامل مع إدخال المستخدم، الرسوم المتحركة، ورسم الصفحة. عندما تحدث مهام ثقيلة هناك (تحليل بيانات كبيرة، معالجة صور، حسابات معقدة)، يمكن أن تتلعثم واجهة المستخدم أو "تتجمد". الWorkers موجودون لنقل مهام معينة بعيدًا عن الخيط الرئيسي أو خارج سيطرة الصفحة المباشرة، حتى يبقى تطبيقك مستجيبًا.
إذا كانت صفحتك منشغلة بحساب يستغرق 200 ملّي ثانية، لا يستطيع المتصفح التمرير بسلاسة، أو الاستجابة للنقرات، أو الحفاظ على 60 إطارًا في الثانية للرسوم المتحركة. تساعد العمال بتمكينك من تنفيذ العمل في الخلفية بينما يركز الخيط الرئيسي على الواجهة.
Web Worker هو سطر JavaScript في الخلفية تنشئه من صفحة. هو الأنسب للمهام المكثفة على المعالج التي كانت ستعيق واجهة المستخدم.
Service Worker هو نوع خاص من العامل يجلس بين تطبيق الويب والشبكة. يمكنه اعتراض الطلبات، تخزين الاستجابات مؤقتًا، وتمكين ميزات مثل العمل دون اتصال والإشعارات الدفعية.
فكّر في Web Worker كمساعد يعمل في غرفة أخرى. ترسله برسالة، يعمل، ثم يرد برسالة.
فكّر في Service Worker كحارس عند الباب الأمامي. تمر الطلبات للصفحات والسكريبتات واستدعاءات API بجانبه، ويمكنه أن يقرر ما إذا كان سيجلب من الشبكة، يخدم من الكاش، أو يرد بطريقة مخصصة.
بنهاية القراءة، ستعرف:
postMessage) نموذج العمال، ولماذا تهم Cache Storage API للدعم دون اتصالهذه النظرة تضع "السبب" والنموذج الذهني—بعدها سنغوص في كيفية تصرف كل نوع عامل وأين يناسب في مشاريع حقيقية.
عندما تفتح صفحة ويب، معظم ما "تشعر" به يحدث على الخيط الرئيسي. هو المسؤول عن رسم البكسلات (الرندر)، الاستجابة للنقرات واللمسات (الإدخال)، وتشغيل الكثير من JavaScript.
لأن الرندر، والتعامل مع الإدخال، وجافاسكريبت غالبًا ما تتناوب على نفس الخيط، يمكن لمهمة واحدة بطيئة أن تجعل كل شيء آخر ينتظر. لهذا السبب تظهر مشاكل الأداء عادةً كمشاكل في الاستجابة، وليس فقط "كود بطيء".
ما الذي يشعر به المستخدم عند الحظر:
جافا سكريبت لديها العديد من واجهات برمجة التطبيقات غير المتزامنة—fetch()، المؤقتات، الأحداث—التي تساعد على تجنّب الانتظار. لكن عدم التزامن لا يجعل العمل الثقيل يحدث في نفس وقت الرندر بطريقة سحرية.
إذا نفّذت عمليات حسابية باهظة (معالجة صور، تحليل JSON ضخم، تشفير، فلترة معقدة) على الخيط الرئيسي، فسيظل يتنافس مع تحديثات واجهة المستخدم. "غير متزامن" قد يؤخّر وقت التنفيذ، لكنه قد يظل يعمل على الخيط الرئيسي ويسبب التقطّع عندما يُنفّذ.
العمال موجودون حتى يتمكّن المتصفح من إبقاء الصفحة مستجيبة مع استمرار القيام بعمل مهم.
باختصار: العمال طريقة لحماية الخيط الرئيسي حتى يبقى تطبيقك تفاعليًا أثناء القيام بعمل حقيقي في الخلفية.
Web Worker هو وسيلة لتشغيل JavaScript خارج الخيط الرئيسي. بدلًا من التنافس مع عمل واجهة المستخدم (الرندر، التمرير، الاستجابة للنقرات)، يعمل العامل في خيط خلفي خاص به حتى تكتمل المهام الثقيلة دون أن تجعل الصفحة "معلقة".
فكّر به كالتالي: تبقى الصفحة مركزة على تفاعل المستخدم، بينما يتولى العامل مهامًا مكثفة مثل تحليل ملف كبير، إجراء حسابات، أو إعداد بيانات للمخططات.
يعمل Web Worker في خيط منفصل مع نطاق عام خاص به. لا يزال لديه وصول إلى العديد من واجهات الويب (المؤقتات، fetch في العديد من المتصفحات، crypto، إلخ)، لكنه معزول عن الصفحة عن عمد.
هناك نوعان شائعان:
إذا لم تستخدم العمال من قبل، معظم الأمثلة التي ستراها تكون لدوال مخصصة (Dedicated Workers).
العمال لا ينادون دوال الصفحة مباشرة. بدلًا من ذلك، يحدث التواصل عن طريق إرسال رسائل:
postMessage().postMessage().للبيانات الثنائية الكبيرة، يمكنك تحسين الأداء غالبًا بنقل ملكية ArrayBuffer (حتى لا يُنسخ)، مما يحافظ على سرعة تمرير الرسائل.
لأن العامل معزول، هناك بعض القيود الأساسية:
window أو document. يعمل العامل تحت self (نطاق العامل العام)، وقد تختلف واجهات API المتاحة عن الصفحة الرئيسية.عند الاستخدام الصحيح، يعد Web Worker من أبسط الطرق لتحسين أداء الخيط الرئيسي دون تغيير ما يفعله تطبيقك—بل أين يحدث العمل المكلف.
Web Workers مناسبة جدًا عندما تشعر أن صفحتك "تتوقف" لأن جافا سكريبت تقوم بالكثير من العمل على الخيط الرئيسي. الخيط الرئيسي مسؤول أيضًا عن تفاعلات المستخدم والرندر، لذا المهام الثقيلة هناك يمكن أن تسبب تقطّعًا، تأخر النقرات، وتجمّد التمرير.
استخدم Web Worker عندما يكون لديك عمل مكثف على المعالج ولا يحتاج وصولًا مباشرًا إلى DOM:
مثال عملي: إذا استقبلت حزمة JSON كبيرة وتحليله يسبب تقطّع الواجهة، انقل التحليل إلى عامل، ثم أرسل النتيجة مرة أخرى.
التواصل مع العامل يتم عبر postMessage. للبيانات الثنائية الكبيرة، فضّل الكائنات القابلة للنقل (مثل ArrayBuffer) حتى يتمكن المتصفح من تسليم ملكية الذاكرة إلى العامل بدلًا من نسخها.
// main thread
worker.postMessage(buffer, [buffer]); // transfers the ArrayBuffer
هذا مفيد جدًا لمخازن الصوت، بايتات الصور، أو قطع بيانات كبيرة أخرى.
العمال لديهم تكلفة: ملفات إضافية، تمرير رسائل، وتدفق تصحيح أخطاء مختلف. تجنّبهم عندما:
postMessage المستمر قد يزيل الفائدة.إذا كانت المهمة يمكن أن تسبب توقفًا ملحوظًا (غالبًا ~50ms+) ويمكن التعبير عنها كـ "مدخل → معالجة → مخرج" دون الوصول إلى DOM، فغالبًا يستحق استخدام Web Worker. إذا كانت المهمة تتعلق بتحديثات واجهة المستخدم في المقام الأول، فحافظ عليها على الخيط الرئيسي وحسّن هناك بدلًا من نقلها.
Service Worker هو ملف JavaScript خاص يعمل في خلفية المتصفح ويعمل كـ طبقة شبكة قابلة للبرمجة لموقعك. بدلًا من التشغيل داخل الصفحة نفسها، يجلس بين تطبيق الويب والشبكة، مما يتيح لك تقرير ما يحدث عندما يطلب التطبيق موارد (HTML، CSS، استدعاءات API، صور).
لـ Service Worker دورة حياة منفصلة عن أي تبويب واحد:
بما أن المتصفح يمكنه إيقافه وإعادة تشغيله في أي وقت، عامل كملف مدفوع بالأحداث: نفّذ العمل بسرعة، خزّن الحالة في تخزين دائم، وتجنّب الافتراض أنه يعمل دائمًا.
Service Workers مقيدة إلى نفس الأصل (نفس النطاق/البروتوكول/المنفذ) وتتحكم فقط في الصفحات ضمن نطاقها—غالبًا المجلد الذي يُقدّم منه ملف العامل (وتحت). كما تتطلب HTTPS (باستثناء localhost) لأنها تؤثر على طلبات الشبكة.
يُستخدم Service Worker بشكل رئيسي للجلوس بين تطبيقك والشبكة. يمكنه أن يقرر متى يستخدم الشبكة، متى يستخدم البيانات المخزنة مؤقتًا، ومتى يقوم ببعض العمل في الخلفية—دون حظر الصفحة.
أكثر المهام شيوعًا هي تمكين تجارب دون اتصال أو عند اتصال ضعيف عن طريق تخزين الأصول والاستجابات.
بعض استراتيجيات التخزين الشائعة:
عادةً ينفّذ هذا باستخدام Cache Storage API وتعامل مع حدث fetch.
يمكن لـ Service Workers تحسين السرعة المُدْركة في الزيارات التالية عن طريق:
النتيجة: طلبات أقل للشبكة، بدء أسرع، وأداء أكثر اتساقًا على الاتصالات غير المستقرة.
يمكن لـ Service Workers تمكين قدرات في الخلفية مثل الإشعارات الدفعية والمزامنة في الخلفية (الدعم يختلف حسب المتصفح والمنصة). هذا يعني أنه يمكنك إعلام المستخدمين أو إعادة محاولة طلب فشل لاحقًا—حتى لو لم تكن الصفحة مفتوحة حاليًا.
إذا كنت تبني تطبيق ويب تقدمي، فـ Service Workers جزء أساسي خلف:
إذا حفظت شيء واحد: Web Workers تساعد صفحتك على تنفيذ عمل ثقيل بدون تجميد الواجهة، بينما Service Workers تساعد تطبيقك على التحكم في طلبات الشبكة والتصرّف كتطبيق قابل للتثبيت (PWA).
Web Worker مخصص للمهام المكثفة على المعالج—تحليل بيانات كبيرة، توليد مصغّرات، عمليات حسابية—حتى يبقى الخيط الرئيسي مستجيبًا.
Service Worker مخصص للتعامل مع الطلبات ومهام دورة حياة التطبيق—الدعم دون اتصال، استراتيجيات الكاش، المزامنة في الخلفية، والإشعارات. يجلس بين تطبيقك والشبكة.
Web Worker مرتبط غالبًا بالصفحة/التبويب. عندما تغلق الصفحة، عادةً ينتهي العامل (ما لم تكن تستخدم SharedWorker أو حالات خاصة).
Service Worker مدفوع بالأحداث. يمكن للمتصفح تشغيله لمعالجة حدث (مثل fetch أو push)، ثم إيقافه عند الخمول، ما يعني أنه يمكن أن يعمل حتى عندما لا تكون هناك تبويبات مفتوحة.
Web Worker لا يمكن أن يعترض طلبات الشبكة التي تجريها الصفحة. يمكنه استخدام fetch() لجلب بيانات، لكنه لا يستطيع إعادة كتابة أو تخزين أو تقديم استجابات للأجزاء الأخرى من موقعك.
Service Worker يمكنه اعتراض طلبات الشبكة (عبر حدث fetch)، ويقرر ما إذا كان يذهب للشبكة، يخدم من الكاش، أو يعيد بديلًا.
Web Worker لا يدير التخزين المؤقت لطلبات HTTP لتطبيقك.
Service Worker غالبًا يستخدم Cache Storage API لتخزين أزواج الطلب/الاستجابة—وهذا أساس التخزين المؤقت دون اتصال والتحميل الفوري عند الزيارات المتكررة.
تشغيل عامل يعتمد على أين يعمل وكيف يُحمّل. تُنشأ Web Workers مباشرة من سكربت الصفحة. تُسجّل Service Workers من الصفحة ويقوم المتصفح بتثبيتها وتجعلها أمام الطلبات لموقعك.
يبدأ Web Worker عندما تنشئه الصفحة. تشير إلى ملف جافاسكربت منفصل، ثم تتواصل عبر 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);
};
نموذج ذهني جيد: ملف العامل هو مجرد عنوان سكربت آخر يمكن للصفحة جلبه، لكنه يعمل خارج الخيط الرئيسي.
يجب تسجيل Service Workers من صفحة يزورها المستخدم:
// main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
بعد التسجيل، يتعامل المتصفح مع دورة التثبيت/التفعيل. يمكن لـ sw.js الاستماع لأحداث مثل install، activate، وfetch.
يمكن لـ Service Workers اعتراض طلبات الشبكة وتخزين الاستجابات. إذا سُمح بالتسجيل عبر HTTP، يمكن لمهاجم على الشبكة استبدال sw.js خبيثًا والتحكم في الزيارات المستقبلية. HTTPS (أو http://localhost أثناء التطوير) يحمي السكربت وحركة المرور التي يمكن أن يؤثر عليها.
المتصفحات تخزن وتحدّث العمال بشكل مختلف عن سكربتات الصفحة العادية. خطّط للتحديثات:
sw.js أو حزمة عامل جديدة).إذا أردت استراتيجية نشر أنعم لاحقًا، راجع /blog/debugging-workers لعادات اختبار تلتقط حالات حافة التحديث مبكرًا.
تفشل العمال بطرق مختلفة عن جافاسكربت "العادي": تعمل في سياقات منفصلة، لديها كونسول خاص بها، ويمكن إعادة تشغيلها من قبل المتصفح. روتين تصحيح جيد يوفر ساعات عمل.
افتح DevTools وابحث عن أهداف العامل. في Chrome/Edge، سترى العمال غالبًا مُدرجة تحت Sources (أو عبر مدخل "Dedicated worker") وفي محدد سياق الكونسول.
استخدم نفس الأدوات التي تستخدمها على الخيط الرئيسي:
onmessage والدوال الطويلة.إذا بدت الرسائل "ضائعة"، افحص الطرفين: تحقق من أنك تنادي worker.postMessage(...)، وأن لدى العامل self.onmessage = ...، وأن شكل الرسالة متطابق.
Service Workers تُصحح أفضل في لوحة Application:
أيضًا راقب Console لأخطاء التثبيت/التفعيل/الـfetch—غالبًا ما تفسر سبب عدم عمل التخزين المؤقت أو السلوك دون اتصال.
مشاكل التخزين المؤقت هي أكبر مستنزف للوقت: تخزين الملفات الخاطئة (أو بشكل متسرّع) يمكن أن يبقي HTML/JS قديمًا. أثناء الاختبار، جرّب إعادة تحميل صلبة وتأكد مما يتم تقديمه فعليًا من الكاش.
للاختبار الواقعي، استخدم DevTools لـ:
إذا كنت تتكرّر بسرعة على PWA، قد يساعدك إنشاء تطبيق أساس نظيف (مع Service Worker وخرائط بناء متوقعة) ثم صقل استراتيجيات التخزين المؤقت من هناك. منصات مثل Koder.ai قد تكون مفيدة لهذا النوع من التجريب: يمكنك نموذج تطبيق React من موجه دردشة، تصدير الشيفرة، ثم تعديل إعدادات العامل والتخزين المؤقت مع حلقة تغذية راجعة أقصر.
يمكن للعمال جعل التطبيقات أكثر سلاسة وقدرة، لكنهم يغيرون أين يعمل الكود وما الذي يمكنه الوصول إليه. فحص سريع للأمن والخصوصية والأداء سيوفر عليك أخطاء مفاجئة—ومستخدمين غير سعداء.
كل من Web Workers و Service Workers مقيدون بسياسة نفس الأصل: يمكنهم فقط التفاعل مباشرةً مع موارد من نفس المخطط/المضيف/المنفذ (إلا إذا سمح الخادم بالوصول عبر CORS). هذا يمنع العامل من سحب بيانات من موقع آخر وخلطها في تطبيقك بصمت.
Service Workers لديها حواجز إضافية: عمومًا تتطلب HTTPS (أو localhost أثناء التطوير) لأنها يمكن أن تعترض طلبات الشبكة. عاملها ككود مميز: حافظ على التبعيات قليلة، تجنب تحميل كود ديناميكي، وأدر إصدارات منطق التخزين بعناية حتى لا تبقى الكاشات القديمة تخدم ملفات منتهية.
الميزات في الخلفية يجب أن تبدو متوقعة. الإشعارات الدفعية قوية، لكن نوافذ الأذونات سهلة الاستغلال.
اطلب الإذن فقط عندما يكون هناك فائدة واضحة (على سبيل المثال، بعد أن يفعل المستخدم الإشعارات في الإعدادات)، وفسّر ما الذي سيستقبله. إذا قمت بالمزامنة أو جلب البيانات في الخلفية، اشرح ذلك بلغة بسيطة—المستخدمون يلاحظون نشاط شبكة غير متوقع أو إشعارات.
العمال ليسوا "مجانيين" للأداء. الإفراط في استخدامها قد ينعكس سلبًا:
postMessage المتكررة (خصوصًا مع كائنات كبيرة) يمكن أن تصبح عنق زجاجة. فضّل التجميع واستخدام الكائنات القابلة للنقل عند الحاجة.ليست كل المتصفحات تدعم كل الإمكانيات (أو قد يمنع المستخدم الأذونات). افحص الميزات وتدهور بأناقة:
if ('serviceWorker' in navigator) {
// register service worker
} else {
// continue without offline features
}
الهدف: يجب أن تعمل الوظائف الأساسية، مع إضافة "تحسينات جميلة" (دون اتصال، دفع، حسابات ثقيلة) عندما تكون متاحة.
Web Workers و Service Workers يحلان مشاكل مختلفة، لذلك يتكاملان جيدًا عندما يحتاج التطبيق إلى المعالجة الثقيلة وتحميل سريع وموثوق. نموذج ذهني جيد: Web Worker = الحوسبة، Service Worker = الشبكة + التخزين المؤقت، الخيط الرئيسي = الواجهة.
افترض أن تطبيقك يتيح للمستخدمين تحرير الصور (تغيير الحجم، الفلاتر، إزالة الخلفية) وعرض معرض لاحقًا دون اتصال.
هذا النهج "الحوسبة ثم التخزين" يحافظ على وضوح المسؤوليات: العامل ينتج المخرجات، وعامل الخدمة يقرر كيف يخزنها ويقدّمها.
لتطبيقات الخلاصات أو النماذج أو بيانات الحقول:
حتى بدون مزامنة كاملة في الخلفية، يحسّن العامل الخدمة الأداء المُدرك من خلال تقديم استجابات مخزّنة أثناء تحديث التطبيق في الخلفية.
تجنب خلط الأدوار:
postMessage).لا. يعمل Service Worker في الخلفية، منفصلًا عن أي تبويب، ولا يمكنه الوصول مباشرةً إلى عناصر DOM (HTML) في الصفحة.
هذا الانفصال مقصود: تم تصميم Service Workers لتستمر في العمل حتى عندما لا تكون هناك صفحة مفتوحة (على سبيل المثال، للاستجابة إلى حدث push أو لخدمة ملفات مخزنة). ولأنّه قد لا يكون هناك وثيقة نشطة للتلاعب بها، يحتفظ المتصفح بهذا العزل.
إذا احتاج Service Worker للتأثير على ما يراه المستخدم، فإنه يتواصل مع الصفحات عبر الرسائل (مثل postMessage) حتى تقوم الصفحة بتحديث الواجهة.
لا. Web Workers و Service Workers ميزتان مستقلتان.
يمكنك استخدام أيٍّ منهما بمفرده، أو معًا حسب الحاجة.
في المتصفحات الحديثة، Web Workers مدعومة على نطاق واسع وتُعد خيارًا آمنًا كقاعدة أساسية.
Service Workers مدعومة أيضًا في إصدارات حديثة من المتصفحات الرئيسية، لكن هناك متطلبات وحالات حافة:
localhost أثناء التطوير).إذا كانت التوافقية الواسعة مهمة، عامل ميزات Service Worker كتطوير تدريجي: بنِ تجربة أساسية جيدة أولًا، ثم أضف الدعم دون اتصال/الدفع حيثما كان متاحًا.
ليس تلقائيًا.
المكاسب الحقيقية تأتي من استخدام العامل المناسب لعنق الزجاجة المناسب، والقياس قبل وبعد.
استخدم Web Worker عندما يكون لديك عمل مكثف على وحدة المعالجة (CPU) يمكن التعبير عنه كـ مدخل → معالجة → مخرجات ولا يحتاج إلى الوصول إلى DOM.
أمثلة مناسبة: تحليل وتحويل بيانات كبيرة، الضغط/فك الضغط، التشفير، معالجة الصور/الصوت، والفرز/التصفية المعقدة. إذا كان العمل يتعلق بتحديثات واجهة المستخدم أو قراءات/كتابات DOM متكررة، فلن يساعد العامل (ولن يستطيع الوصول إلى DOM على أي حال).
استخدم Service Worker عندما تحتاج إلى التحكم في الشبكة: دعم العمل دون اتصال، استراتيجيات التخزين المؤقت، تسريع الزيارات المتكررة، توجيه الطلبات، وأيضًا (عند الدعم) الإشعارات والدفع والمزامنة في الخلفية.
إذا كانت مشكلتك "تتجمد الواجهة أثناء الحساب" فهذه مشكلة Web Worker. إذا كانت مشكلتك "التحميل بطيء/الوضع دون اتصال لا يعمل" فهذه مشكلة Service Worker.
لا. Web Workers و Service Workers مستقلتان.
يمكنك استخدام أيٍّ منهما بمفرده، أو كليهما معًا إذا كان تطبيقك يحتاج حوسبة في الخلفية ووظائف شبكة/تخزين مؤقت أيضًا.
الفرق الرئيسي يكمن في النطاق ومدة الحياة.
fetch حتى عندما لا تكون هناك صفحة مفتوحة، ثم يُوقف عند الخمول.لا. Web Workers ليس لديها وصول إلى window/document.
إذا احتجت إلى التأثير على واجهة المستخدم، أَرْسِل البيانات إلى الخيط الرئيسي عبر postMessage()، ثم حدّث DOM في كود الصفحة. احتفظ بالعامل مركزًا على المعالجة النقية.
لا. Service Workers لا تملك وصولًا إلى DOM.
للتأثير على ما يراه المستخدم، تواصل مع الصفحات المُتحكَّم بها عبر الرسائل (مثل Clients API + postMessage())، ودع الصفحة هي التي تحدّث واجهة المستخدم.
استخدم postMessage() على الطرفين.
worker.postMessage(data)self.postMessage(result)لبيانات ثنائية كبيرة، فضّل الكائنات القابلة للنقل (transferables) مثل ArrayBuffer لتجنب النسخ:
تجلس Service Workers بين تطبيقك والشبكة ويمكنها الرد على الطلبات باستخدام Cache Storage API.
استراتيجيات شائعة:
اختر استراتيجية لكل نوع مورد (قشرة التطبيق مقابل بيانات API)، وليس قاعدة واحدة للجميع.
نعم، لكن احرص على فصل المسؤوليات.
نمط شائع:
هذا يساعد على عدم خلط منطق العرض مع منطق الخلفية ويحافظ على أداء متوقع.
استخدم واجهة DevTools المناسبة لكل حالة.
onmessage، وقيّم الأداء لتتأكد أن الخيط الرئيسي يبقى مستجيبًا.عند تصحيح أخطاء الكاش، تحقق دائمًا مما يُقدّم فعليًا (الشبكة أم الكاش) واختبر وضع عدم الاتصال وسرعات الشبكة.
worker.postMessage(buffer, [buffer]);