أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Web Workers في JavaScript: شغّل الحسابات الثقيلة بدون ما الواجهة تتعلّق

📅 ٢٦ أبريل ٢٠٢٦⏱ 6 دقائق قراءة
Web Workers في JavaScript: شغّل الحسابات الثقيلة بدون ما الواجهة تتعلّق

Web Workers في JavaScript: نقل الحسابات الثقيلة بره الـ main thread

المستوى: متوسط — يفترض إن عندك خبرة بـ JavaScript والـ DOM، ومش شرط تكون اشتغلت على threading قبل كده.

لو الواجهة بتعلّق لمدة ثانيتين كل ما المستخدم يضغط زر "تصدير CSV" أو "ولّد التقرير"، المشكلة مش إن الكود بطيء. المشكلة إنه شغّال على نفس الـ thread اللي بيرسم الواجهة. Web Workers هينقل الحسابات الثقيلة في خلفية موازية، والواجهة تفضل تستجيب بـ INP أقل من 200ms حتى وقت الحساب.

المشكلة باختصار

المتصفح بيدّيك thread واحد بس للـ JavaScript على كل tab: الـ main thread. الـ thread ده بيعمل أربع حاجات في نفس الوقت — تنفيذ الكود، رسم الـ DOM، الاستجابة لأحداث المستخدم، وتشغيل الـ animations. لو حساب واحد عدّى 50ms عليه، أي حاجة في الطابور بعده هتتأخر بنفس المقدار. المستخدم بيشوف ده على شكل ضغطات زرار مش بترد، scroll بيقطع، وحقول إدخال بتأكل الحروف. مقياس INP في Core Web Vitals بيقيس الظاهرة دي تحديدًا، والحد الأخضر هو 200ms.

شاشات متعددة تعرض كود JavaScript يوضح فصل الحسابات الثقيلة عن واجهة المستخدم

المثال البسيط الأول: المطبخ والشيف

تخيّل مطعم فيه شيف واحد. لو طلب جالك "بيتزا تحتاج 20 دقيقة فرن"، الشيف هيقعد متابع الفرن طول المدة دي، ومحدش هيقدر يطلب حاجة تانية. المشكلة مش إن الشيف بطيء، المشكلة إنه واحد بس وعنده مهام تانية قاعدة في الطابور. الحل البديهي مش إنك تخلّيه يطبخ أسرع، الحل إنك تجيب عامل تاني معاه فرن منفصل، يستلم الطلب، والشيف يكمل خدمة باقي الزبائن.

الـ main thread هو الشيف. الـ Web Worker هو العامل التاني اللي معاه فرن خاص بيه. الفكرة دي بالظبط اسمها concurrency: مش بنخلّي الحساب أسرع، بنخلّي الواجهة تفضل قادرة ترد وهو شغّال.

التعريف العلمي للـ Web Worker

الـ Web Worker هو سياق تنفيذ JavaScript مستقل، بيشتغل في OS thread منفصل عن الـ main thread، ومعاه global scope خاص بيه. الـ worker معندوش وصول مباشر للـ DOM ولا للـ window ولا لـ document. التواصل بين الـ main thread والـ worker بيحصل بآلية message passing عبر الدالتين postMessage() و onmessage، مع نسخ البيانات افتراضيًا باستخدام structured clone، أو نقل ملكيتها بدون نسخ عبر transferable objects للأنواع زي ArrayBuffer.

مثال تنفيذي: عملية حساب ثقيلة على الكلاينت

هنفترض إن عندك صفحة لتحليل بيانات بتنفّذ حساب recursive ثقيل (فيبوناتشي 42 كنموذج بسيط لأي حساب CPU-bound). هنبني الكود مرتين، مرة على الـ main thread ومرة على worker، ونقيس INP بين الحالتين.

الكود قبل التحسين — كل حاجة على الـ main thread

JavaScript
// app.js
function fib(n) {
  if (n < 2) return n;
  return fib(n - 1) + fib(n - 2);
}

document.querySelector('#run').addEventListener('click', () => {
  console.time('fib');
  const result = fib(42);
  console.timeEnd('fib');
  document.querySelector('#out').textContent = result;
});

القياس على Chrome 130، MacBook M2: زمن الحساب 1,820ms. الواجهة جامدة بالكامل خلال المدة دي، أي click تاني بيتجاهل، والـ scroll بيقطع. الـ INP بيطلع فوق 1,500ms — منطقة حمراء بالكامل في Core Web Vitals.

الكود بعد التحسين — الحساب على worker

JavaScript
// worker.js — ملف منفصل
self.onmessage = (event) => {
  function fib(n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
  }
  const result = fib(event.data);
  self.postMessage(result);
};
JavaScript
// app.js — الـ main thread بقى يستدعي الـ worker
const worker = new Worker('/worker.js');

document.querySelector('#run').addEventListener('click', () => {
  worker.postMessage(42);
  document.querySelector('#out').textContent = 'بيحسب...';
});

worker.onmessage = (event) => {
  document.querySelector('#out').textContent = event.data;
};

القياس على نفس الجهاز: زمن الحساب الفعلي فضل قريب من 1,820ms — الـ worker مش بيخلي الخوارزمية أسرع، بيخلي الـ main thread متفرّغ بس. النتيجة الفعلية اللي بتفرق مع المستخدم: الـ INP نزل لـ 40ms، الـ scroll مبيقطعش، وأي click تاني بيرد في إطار واحد. تجربة المستخدم اتغيّرت كليًا رغم إن الزمن الكلي للحساب نفسه ما اتحسّنش.

لوحة قياس أداء تعرض زمن استجابة قبل وبعد استخدام Web Workers

الـ trade-off هنا بالتفاصيل

  • المكسب الأساسي: الواجهة بتفضل مستجيبة، INP تحت 200ms، Core Web Vitals بتتحسّن، ومعدل الإكمال بيزيد لأن المستخدم مبيحسش إن الموقع متعلّق.
  • التكلفة الأولى — إنشاء الـ worker: فتح worker جديد بياخد 5–15ms على متصفح متوسط. لو المهمة أصلًا أقل من 50ms، التكلفة دي هتاكل المكسب.
  • التكلفة التانية — نقل البيانات: الرسائل بين الـ threads بتتنسخ افتراضيًا (structured clone). ArrayBuffer حجمه 100MB ممكن ياخد 80–120ms في النسخ لوحده. الحل: استخدم worker.postMessage(buffer, [buffer]) عشان تنقل الملكية بدل النسخ، فيرجع الزمن لأقل من 1ms، لكن الـ buffer ما بيبقاش متاح في الـ main thread بعدها.
  • التكلفة التالتة — التعقيد: debugging أصعب، الكود بقى رسائل غير متزامنة، والـ worker مقدرش يلمس DOM. لو محتاج تعدّل DOM، لازم ترجع رسالة للـ main thread وتعدّله من هناك.

متى لا تستخدم Web Workers

  • المهمة أصلًا أسرع من 50ms — تكلفة إنشاء الـ worker ونقل الرسائل هتفوق المكسب.
  • محتاج تعديل DOM مكثّف ومتكرر — الذهاب والإياب بين الـ threads هيكون أبطأ من التنفيذ المباشر.
  • الكود بيعتمد على مكتبة تستخدم window أو document بدون بديل — الـ worker مش هيقدر يشغّلها.
  • التطبيق هدفه IO-bound (طلبات شبكة، انتظار قاعدة بيانات) — في الحالة دي async/await العادي بيحرّر الـ main thread بدون أي تكلفة worker.
  • الجهاز هدف ضعيف جدًا (mobile قديم) — تكلفة إنشاء worker إضافية بتكون محسوسة.

متى تستخدمه فعلًا

  • معالجة صور أو فيديو على المتصفح (canvas filters، image resize، مكتبات زي pica).
  • parsing لملفات كبيرة (CSV أو JSON أكبر من 5MB) — مع مكتبات زي PapaParse في Web Worker mode.
  • تشفير وفك تشفير من جانب العميل (libsodium، end-to-end encryption في تطبيقات المراسلة).
  • algorithms ثقيلة زي pathfinding في الألعاب، simulations، حسابات إحصائية، أو تحليل بيانات تفاعلي.
  • WebAssembly modules لها تنفيذ طويل — تشغيلها على worker بيحرّر الواجهة.

الخطوة التالية

افتح DevTools على أبطأ صفحة عندك، شغّل Performance recording، ودوّر على أي task مدته فوق 50ms في الـ main thread (التاسكات الطويلة بتتلوّن أحمر). لو لقيت ثلاث تاسكات منهم، خد أكبر واحد فيهم وانقله لـ worker بالقالب اللي فوق. شغّل القياس تاني وقارن INP. لو نزل تحت 200ms، انت محقق المكسب الأساسي. لو ما اتغيّرش، غالبًا المشكلة في DOM updates أو layout thrashing مش في حساب CPU، ولازم تنظر لها بشكل مختلف.

المصادر

  • MDN Web Docs — Using Web Workers: developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
  • web.dev — Use Web Workers to run JavaScript off the browser's main thread: web.dev/articles/off-main-thread
  • HTML Living Standard — Web Workers: html.spec.whatwg.org/multipage/workers.html
  • web.dev — Interaction to Next Paint (INP): web.dev/articles/inp
  • Chrome Developers — Optimize long tasks: developer.chrome.com/docs/lighthouse/performance/long-tasks-devtools
  • MDN — Transferable objects: developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة