المرحلة 0 · درس تأسيسي · الدرس 0006 · ~45 دقيقة

الـ Callbacks من الصفر — الدوال لما تبقى قيم بتتبعت

الدرس ده اتولد بطلب منك: حسيت إن أساس الـ callbacks مهزوز فوقّفنا كل حاجة وبنبنيه صح. بعده هتفهم فخ الـ this فهم كامل، وهو نفسه أول حلقة في سلسلة الـ async (الأسبوع 6 إن شاء الله) — يعني استثمار مزدوج.

🔁 قبل ما تبدأ: الدرس ده مالوش prerequisites غير أساسيات الدوال والـ scope (خلصتهم كلهم ✓). ادخل على طول — وسيبك من أي حاجة قريتها عن callbacks قبل كده، هنبني من الصفر.

١ — الأساس اللي كل حاجة مبنية عليه: الدالة قيمة

في JS، الدالة قيمة زيها زي الرقم والنص بالظبط — تتخزن في متغير، تتحط في أوبجكت، وتتبعت كـ argument لدالة تانية. (الاسم العلمي: first-class functions — الدوال مواطنين درجة أولى.)

عشان كده الكود ده شغال عادي جداً:

function sayHi() { console.log("هاي"); }
const g = sayHi;
g();   // "هاي"

السطر التاني نسخ قيمة الدالة جوه المتغير g، والسطر التالت نده عليها من اسمها الجديد فطبعت "هاي" عادي.

٢ — أهم تفرقة في الدرس كله: f غير f()

الكتابةمعناهابتديك إيه
sayHi (من غير أقواس)"الدالة نفسها كقيمة"مؤشر للدالة — ماتنفذتش
sayHi() (بالأقواس)"نفّذها دلوقتي حالاً"ناتج تنفيذها

الأقواس () هي أمر التشغيل. من غيرها أنت بتشاور على الدالة، بيها أنت بتشغّلها. والخلط بينهم أشهر باج مبتدئين في التاريخ:

setTimeout(sayHi, 1000);    // ✓
setTimeout(sayHi(), 1000);  // 💥 TypeError

السطر الأول بعت الدالة كقيمة، فهتتنفذ بعد ثانية زي ما متوقع. أما السطر التاني: الأقواس نفّذت sayHi فوراً في نفس اللحظة، وبالتالي setTimeout استلمت ناتج التنفيذ (اللي هو undefined) بدل ما تستلم الدالة — فرمت TypeError.

✓ الباج ده متحقق منه بتشغيل Node فعلي (2026-08-05): طبعت فوراً وبعدين انفجر البرنامج.

٣ — يبقى إيه الـ Callback بقى؟

① الـ Callback = دالة بتبعتها كقيمة لدالة تانية، عشان التانية دي تناديها هي في الوقت المناسب.

② الدالة المستقبِلة (اللي بتاخد دالة كـ argument) اسمها Higher-Order Function — "دالة من رتبة أعلى" لأنها بتتعامل مع دوال مش بس بيانات.

③ الاسم نفسه بيشرح: call back = "عاود الاتصال" — أنت بتسيب رقمك (الدالة) وبتقول "اتصل بيا لما يحصل كذا".

٤ — فك السحر: ابني higher-order function بإيدك

أكبر سبب للغموض إن الناس بتشوف callbacks جوه أدوات جاهزة (setTimeout, forEach) وفاكرة إن فيه سحر جواها. مفيش — المستقبِل بينده باراميتره وخلاص، شوف:

function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    action(i);
  }
}

function sayNum(i) { console.log("رقم " + i); }

repeat(3, sayNum);

اقرا repeat كإنك المحرك: action باراميتر عادي جداً استلم دالة كقيمة، والسطر action(i) بينده الباراميتر ده نداء عادي — لأنه أصلاً دالة. الناتج: "رقم 0" ثم "رقم 1" ثم "رقم 2". كل higher-order function في الدنيا — من setTimeout لـ forEach — جواها نفس الفكرة دي بالظبط. ✓ متحقق بالتشغيل

🌍 وده اللي بتستخدمه يومياً من غير ما تحس:

items.forEach(print);                     // ناديها على كل عنصر
button.addEventListener("click", save);   // ناديها لما يدوس
setTimeout(remind, 1000);                 // ناديها بعد ثانية

كلهم نفس العقد: "خد الدالة دي، وناديها أنت لما الشرط بتاعك يتحقق". (تفاصيل إزاي setTimeout بتستنى من غير ما توقف البرنامج — دي حكاية الـ Event Loop في الأسبوع 6 إن شاء الله.)

٥ — شوفها بعينك: رحلة الـ callback جوه المحاكي

سيناريو A بيوريك الميكانيكا النضيفة، وسيناريو B بيوصّلها بفخ الـ this اللي قابلته في 0005. راقب باراميتر action جوه الـ frame — هتشوف الدالة واصلة كقيمة!

function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    action(i);
  }
}
function sayNum(i) {
  console.log("رقم " + i);
}
repeat(2, sayNum);
دوس «خطوة» عشان نبدأ.
Console:
Call Stack + جوه كل context

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

function greet() { console.log("أهلاً"); }
setTimeout(greet(), 500);
function twice(fn) { fn(); fn(); }
twice(function () { console.log("تم!"); });
function run(action) { action(); }
const user = { name: "عمر", hi() { console.log("أهلاً " + this.name); } };
run(user.hi);

٧ — اختبر نفسك

س1: إيه الفرق بين sayHi و sayHi() كـ argument؟

س2: يعني إيه higher-order function؟

س3: جوه repeat، السطر action(i) بيعمل إيه؟

س4 (الربط بدرس this): ليه run(user.hi) بتطبع undefined؟

٨ — التحدي الأخير: ابني بإيدك (من غير AI!)

المرة دي مش شرح — كتابة كود. افتح أي ملف أو console واكتب بنفسك (بالترتيب):

① عرّف دالة بسيطة اسمها sayHi بتطبع أي رسالة — دي هتبقى "فار التجارب" بتاعنا.

② اكتب دالة اسمها callSafely(fn): بتستقبل قيمة — لو اللي وصلها فعلاً دالة (typeof fn === "function") تناديها، ولو لأ تطبع "دي مش دالة!".

③ جرّبها بالتلات حالات دول وتوقع الناتج قبل كل واحدة: callSafely(sayHi) ثم callSafely(sayHi()) ثم callSafely("نص").

ابعتلي الكود اللي كتبته ونتايج التجارب التلاتة — ودي هتبقى أول مهمة كتابة كود بينا، على قد ما هي بسيطة على قد ما بتثبت الفكرة كلها.

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