المرحلة 0 · الأسبوع 3 · الدرس 0012 · تقفيلة الأسبوع · ~40 دقيقة

Module Pattern — تقفيلة أسبوع 3

ليه الدرس ده؟ في 0010 عملت createAccount — دالة تقدر تناديها كذا مرة، كل مرة بترجع حساب مستقل بـ private state خاصة بيه. لو محتاج بس نسخة واحدة — object واحد يمثل جزء كامل من برنامجك (إعدادات، سلة تسوق، إلخ) وعايزه يبقى خاص من غير ما تنادي دالة بالاسم بعدين — بتستخدم أداة جديدة اسمها IIFE (هنشرحها من الصفر) عشان تعمل نفس فكرة الـ closure لكن بتنفيذ فوري. ده كان الحل التاريخي لتنظيم كود JS في وحدات منفصلة قبل ما يظهر ES Modules الرسمي (import/export) اللي هناخده في أسبوع 8 إن شاء الله.

🔁 مراجعة سريعة قبل ما تبدأ (~5 دقايق) — بالأمثلة:

من 0010 — private state بـ factory function (createAccount):

function createAccount(initialBalance) {
  let balance = initialBalance;
  return {
    deposit(amount) { balance += amount; return balance; },
    getBalance() { return balance; }
  };
}
const acc = createAccount(500);
acc.deposit(100);
acc.getBalance();
acc.balance;

افتكر من الذاكرة: آخر سطر (acc.balance) بيرجع إيه؟ وليه بالظبط؟ (لو مش متذكر، راجع درس 0010 قبل ما تكمل.)

من 0011 — كل نداء لدالة factory بيرجع بيئة مستقلة:

const x = createAccount(100);
const y = createAccount(100);
x.deposit(50);
x.getBalance();
y.getBalance();

توقع: x.getBalance() وy.getBalance() هيطلعوا نفس الرقم ولا مختلفين؟ ليه؟

١ — الفكرة في 3 جمل

① Module Pattern = استخدام دالة بتتنادى على نفسها فورًا وقت التعريف (IIFE — هنشرحها في القسم الجاي) عشان تعمل نطاق خاص (private scope)، وترجع بس الأجزاء اللي عايز تكشفها للعالم الخارجي كـ object واحد.

② نفس آلية الـ closures بالظبط (0010) — بس بدل ما تكون دالة factory تقدر تناديها كذا مرة (زي createAccount)، هنا الدالة بتتنفذ مرة واحدة بس وقت التعريف، فالنتيجة نسخة واحدة (singleton) مش قالب لعمل نسخ.

③ كان الحل العملي لتنظيم كود JavaScript في "وحدات" منفصلة (كل حاجة خاصة إلا اللي بتقرر تكشفه) قبل ما تظهر ES Modules الرسمية — نفس الهدف (تنظيم + إخفاء تفاصيل)، بأدوات اللغة الأساسية بس.

٢ — أداة جديدة: IIFE (Immediately Invoked Function Expression)

IIFE — تُنطق "آيفي" — اختصار Immediately Invoked Function Expression (دالة تعبيرية بتتنادى فورًا). ببساطة: دالة بتتعرّف وتتنادى في نفس السطر، من غير ما تتخزن في متغير الأول.

جرّب الشكل الطبيعي اللي هيخطر على بالك الأول — دالة وبعدها قوسين نداء على طول:

function () { console.log("hi"); }();

الناتج الحقيقي:

SyntaxError: Function statements require a function name

✓ متحقق بتشغيل Node.js فعلي (2026-08-25).

السبب: من غير أي حاجة قبلها، JS بيقرا function () {...} على إنها function declaration (تصريح دالة) — وأي تصريح دالة لازم يكون ليه اسم. الحل: تحوط الدالة بقوسين ( ) عشان تجبر JS يقرأها كـ function expression (تعبير بيرجع قيمة) بدل تصريح — وتعبير الدالة مسموح يكون anonymous (بلا اسم) ومسموح تنده عليه على طول بقوسين نداء تانيين:

(function () {
  console.log("hi from IIFE");
})();
hi from IIFE

✓ متحقق بتشغيل Node.js فعلي (2026-08-25). الشكل ده مسموح كمان بالـ arrow function اللي عرفتها في 0008: (() => { ... })(); — نفس الفكرة بالظبط.

٣ — المثال الأساسي: عداد بـ Module Pattern

const counter = (function () {
  let count = 0;
  function increment() { count++; return count; }
  function reset() { count = 0; }
  function getCount() { return count; }
  return { increment, reset, getCount };
})();

console.log(counter.increment());
console.log(counter.increment());
console.log(counter.getCount());
counter.reset();
console.log(counter.getCount());
console.log(counter.count);

الناتج الحقيقي سطر سطر:

1
2
2
0
undefined

✓ متحقق بتشغيل Node.js فعلي (2026-08-25). لاحظ: counter.count رجعت undefinedcount مش موجودة على الـ object أصلاً، هي موجودة بس جوه الـ closure اللي الدالتين increment/reset/getCount شايلين رابط ليها. مفيش أي طريقة توصل لها من بره غير الدوال التلاتة دي.

٤ — الفرق عن factory pattern (0010): singleton ضد قالب

الاتنين بيستخدموا نفس آلية الـ closure بالظبط، بس الفرق في امتى بتتنفذ الدالة الخارجية:

Factory (createAccount — 0010)Module Pattern (IIFE)
امتى بتتنادى؟لما إنت تقرر — تقدر تنادي كذا مرةفورًا وقت التعريف — مرة واحدة بس، تلقائيًا
عدد النسخعدد ما تحب — كل نداء نسخة مستقلةنسخة واحدة بس (singleton)
مثال استخدامحساب بنكي لكل مستخدم، عداد لكل مكوّن UIإعدادات التطبيق، namespace لمكتبة كاملة

إثبات إن IIFE بترجع نسخة واحدة بس — مفيش طريقة تاخد نسخة تانية من غير ما تنسخ الكود كله وتشغله تاني:

console.log(typeof counter);
object

✓ متحقق بتشغيل Node.js فعلي (2026-08-25). counter هو الـ object الناتج نفسه — مش دالة تقدر تنادي عليها تاني زي createAccount().

٥ — شوفها بعينك: التنفيذ الفوري في المحاكي

لاحظ الفرق عن كل محاكيات closures اللي فاتت: مفيش خطوة منفصلة "الدالة اتنادت بالاسم" — النداء بيحصل في نفس سطر التعريف. توقع قبل كل خطوة!

const counter = (function () {
  let count = 0;
  function increment() { count++; return count; }
  return { increment };
})();
counter.increment();
counter.increment();
دوس «خطوة» عشان نبدأ.
Console:
🟦 Call Stack (مؤقت)
🟩 بيئات باقية (بره الـ stack)

٦ — توقع قبل ما تشوف (3 تمارين)

const settings = (function () {
  let theme = "dark";
  return { getTheme() { return theme; } };
})();
console.log(settings.theme);
function () { return 1; }();
const m = (function () { return { x: 1 }; })();
console.log(typeof m);

٧ — اختبر نفسك

س1: IIFE اسمها اختصار لإيه؟

س2: ليه function(){}(); من غير قوسين حوالين الدالة بترمي SyntaxError؟

س3: الفرق الجوهري بين Module Pattern (IIFE) وfactory function (0010)؟

س4 (سؤال judgment): امتى تختار Module Pattern بدل factory function؟

٨ — تقفيلة أسبوع 3 (من غير AI!)

الجزء الأول — تحدي التصميم: ابني module لسلة تسوق (shopping cart) بـ IIFE، اسمه cart. المطلوب:

جرّب: ضيف 3 عناصر، شيل واحد، اطبع الباقي — تأكد إن النتيجة صح.

الجزء التاني — تقفيلة الأسبوع (شرح في 3 جمل): من غير كود، اكتب (أو قول بصوت عالي لنفسك الأول وبعدين اكتب هنا) 3 جمل بس تربط الرحلة كاملة من أول الأسبوع لحد دلوقتي: lexical scope (0010) ← closures (0010) ← currying (0011) ← module pattern (0012). مش مطلوب تفاصيل تقنية دقيقة — مطلوب تربط الفكرة الكبيرة: إزاي كل حاجة في الأسبوع ده مبنية فوق نفس المبدأ الواحد.

٩ — المصادر الأساسية للتعمق