المرحلة 0 · الأسبوع 2 · الدرس 0007 · ~40 دقيقة

call / apply / bind — التحكم اليدوي في الـ this

ليه الدرس ده؟ في 0005 اتعلمت إن الـ this بيتحدد من شكل النداء، وفي 0006 فهمت آلية الـ callback من جواها وليه المستقبِل بيضيّع الـ this. النهاردة بتاخد عجلة القيادة: تلات أدوات بتتحكم بيهم في الـ this بإيدك — وواحدة منهم هي الحل الجذري للفخ ده.

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

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

call و apply بينفذوا الدالة فوراً بـ this اللي أنت محدده — والفرق الوحيد بينهم شكل تمرير الـ arguments.

bind مختلف جوهرياً: مش بينادي الدالة خالص — بيصنّعلك نسخة جديدة منها الـ this بتاعها مقفول للأبد، وبيرجعهالك تستخدمها بعدين.

③ النسخة المقفولة دي اسمها hard binding: مافيش نداء لاحق يقدر يغير this بتاعها — لا نقطة ولا حتى call تانية.

٢ — التلاتة جنب بعض

بينفذ امتى؟الـ argumentsبيرجع إيه؟
f.call(obj, a, b)فوراًمفصولة بفواصلناتج الدالة نفسها
f.apply(obj, [a, b])فوراًفي array واحدةناتج الدالة نفسها
f.bind(obj)مش بينفذتقدر تثبّت أولها مقدماً (شوف الكارت تحت)دالة جديدة مقفولة الـ this

حيلة الحفظ (بالإنجليزي): Call = Commas (فواصل)، Apply = Array. و bind بمعناها الحرفي: "اربط" — بتربط الدالة بصاحبها وترجعها.

🤔 طب ليه الاتنين موجودين لو بيعملوا نفس الحاجة؟ (سؤال صاحب الورشة) — لأن اللي بيحدد الاختيار هو شكل بياناتك لحظة النداء: call لما الـ arguments معروفة وبتكتبها بإيدك، وapply لما القيم جاية جاهزة في array — وغالباً عددها نفسه مش معروف وأنت بتكتب الكود (جاية من API أو من إدخال المستخدم):

const scores = [7, 3, 9, 2];
Math.max(scores);               // NaN
Math.max.apply(null, scores);   // 9

Math.max بتاخد أرقام مفصولة مش array — السطر التاني اداها الـ array فاتلخبطت ورجّعت NaN، والتالت ناداها بـ apply اللي فكّت الـ array لقيم مفصولة فاشتغلت. (حطينا null مكان الـ this لأن Math.max مش بتستخدمه — نفس حركة bind في كارت الخانات.) ✓ متحقق بالتشغيل (2026-08-06)

🔭 للمستقبل: من 2015 فيه أداة أحدث اسمها spread (...) بتحل نفس المشكلة بشكل أنضف — هتتشرح في الأسبوع 8 إن شاء الله، وهي اللي خلت apply النهارده بتظهر في الكود القديم أكتر من الجديد. بس لازم تفهمها — لأن الكود القديم ده أنت اللي هتقراه وتصلحه في شغلك.

📌 يعني إيه "تثبّت أولها مقدماً"؟ دي خاصية اسمها partial application (التطبيق الجزئي). فكّر في parameters الدالة كـخانات فاضية: multiply(a, b) عندها خانتين. أي قيمة تبعتها لـ bind بعد الـ this بتتحط في أول خانة فاضية وتتملي بشكل دائم:

function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2);
double(5);    // 10
double(100);  // 200

في السطر التاني: الخانة الأولى (a) اتملت بالرقم 2 بشكل دائم. فلما نديت double(5)، الـ 5 راحت للخانة الفاضية (b) واتنفذت multiply(2, 5) فطلعت 10. النسخة الجديدة عندها خانات أقل — أنت بتكمّل الفاضي بس وقت النداء. هترجعلها تاني في تمرين الـ currying بالأسبوع الجاي إن شاء الله. ✓ متحقق بتشغيل Node فعلي (2026-08-05)

✓ كل سلوك في الدرس متحقق منه بتشغيل Node.js فعلي (2026-08-05).

٣ — الجايزة الكبيرة: bind بتحل فخ الـ callback

فاكر الفخ من 0005؟ setTimeout(user.hi, 1000) بيطبع "أهلاً undefined" لأن setTimeout بينادي نداء ساكت. الحل:

setTimeout(user.hi.bind(user), 1000);   // "أهلاً عمر" ✓

اللي حصل: user.hi.bind(user) صنعت نسخة جديدة من الدالة الـ this بتاعها متلحم فيها = user، وبعتنا النسخة دي لـ setTimeout. دلوقتي حتى لما يناديها نداء ساكت، القفل شغال — الـ default binding مالوش سلطة على دالة مربوطة. هتستخدم الحركة دي فعلياً مع الـ events والـ timers في شغلك.

💡 الدقة الكاملة (من نقاش مع صاحب الورشة): الـ this بيضيع لما تبعت مؤشر الدالة نفسه — مش "أي callback". لو الدالة وصلت ومعاها صاحبها والنداء النهائي حصل بالنقطة، الـ implicit binding شغال عادي. صورتين لنفس الفكرة:

setTimeout(() => user.hi(), 100);

function runLater(obj) {
  setTimeout(() => obj.hi(), 100);
}
runLater(user);

السطر الأول هو صورة (أ): غلاف بينده user.hi() بالاسم — وقت التنفيذ، سلسلة البحث (درس 0003) بتطلع للـ scope الخارجي وبتلاقي user. ودالة runLater هي صورة (ب): الأوبجكت واصل كـباراميتر — ودي فكرة صاحب الورشة.

⬇️ الفرق الحقيقي بين التلات حلول (أ وب وbind) بيظهر لما المتغير يتبدل قبل التنفيذ — وده بالظبط موضوع سيناريو C الجديد في المحاكي تحت: بيمشي القصة الكاملة سطر سطر بالكود الكامل، وبيوريك بالألوان مين ماسك أنهي أوبجكت في كل لحظة. ماتحاولش تفهمها من هنا — انزل العبها.

🔭 ملحوظة للمستقبل: في سيناريو C هتقابل سؤال معلق ("إزاي obj فضل موجود بعد ما runLater خلصت؟") — ده بالظبط موضوع الـ closure، وليه أسبوع كامل قدامنا (الأسبوع 3 إن شاء الله). دلوقتي بناخد نتيجته المتحقق منها بالتشغيل، وشرح الآلية هناك.

٤ — شوفها بعينك: القفل 🔒 جوه المحاكي

راقب حاجتين: الـ arguments بتوصل إزاي في call/apply، وإزاي bind بتصنع دالة تانية خالص شايلة قفلها معاها. توقع قبل كل خطوة!

function intro(city, job) {
  console.log(this.name + " من " + city + " — " + job);
}
const omar = { name: "عمر" };
intro.call(omar, "طنطا", "مطور");
intro.apply(omar, ["طنطا", "مطور"]);
دوس «خطوة» عشان نبدأ.
Console:
Call Stack + الـ this جوه كل context

٥ — الاستثناء الوحيد اللي بيكسر القفل: new

شفت في المحاكي فوق إن locked.call({name:"سارة"}) فشلت تكسر القفل. بس قلنا فيه استثناء واحد بس بينجح — وهو مش قاعدة غريبة جديدة، هو نفس جدول الأولويات اللي حفظته في 0005.

🔁 افتكر من الذاكرة قبل ما تكمل: في جدول قواعد الـ this الأربعة، مين كان صاحب الأولوية الأولى؟ ومين التانية؟

(الإجابة: new = أولوية 1، والـ explicit binding — يعني call/apply/bind — = أولوية 2. لو نسيت، ارجع لمحاكي 0005.)

القفل بتاع bind بيقاوم بس محاولات من نفس رتبته (call/apply — أولوية 2 برضه). لكن new أعلى منه في السلم — فلما تستخدمها مع دالة مربوطة، بتغلب القفل. جرّبها بنفسك قبل ما تشوف الناتج:

function Person(name) {
  this.name = name;
}
const locked = Person.bind({ name: "غريب" });
locked.call({ name: "test" });
const p = new locked("سارة");

توقع: p.name هيبقى إيه؟

الناتج الحقيقي (متحقق منه بتشغيل Node فعلي): p.name === "سارة"، وكمان p instanceof Person === true. ✓ متحقق بالتشغيل (2026-08-13)

ليه؟ new مش "نداء تاني للدالة" — هي عملية مختلفة جذرياً: بتصنع أوبجكت فاضي جديد، وبتشغّل الدالة كـ"مصنع" بيبني جواه. الأوبجكت الجديد ده هو اللي بيبقى this — مش الأوبجكت اللي كان متلحم في bind. القفل ماتكسرش فعلياً؛ هو أصلاً معمول عشان يوقف بس محاولات إعادة ربط من نفس رتبته (call/apply)، ومالوش سلطة على حاجة من رتبة أعلى.

دقة إضافية: لو bind كانت حجزت argument مقدماً (partial application، فاكر كارت "الخانات" فوق؟) — new بتلغي بس جزء الـ this، مش الـ arguments المحجوزة:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
const lockedWithArg = Person.bind({ name: "غريب" }, "اسم-محجوز-مسبقاً");
const p2 = new lockedWithArg(25);
p2.name;  // "اسم-محجوز-مسبقاً"
p2.age;   // 25

✓ متحقق بتشغيل Node فعلي (2026-08-13). القفل عنده جزءين مختلفين: this المقفول (ده بس اللي new بيتخطاه) والـ arguments المحجوزة بالـ partial application (دي باقية زي ما هي). عشان كده p2.name طلعت لسه "اسم-محجوز-مسبقاً" (من وقت الـ bind)، بينما p2.age جت 25 (من الـ new دلوقتي).

📌 ملحوظة عملية: الحالة دي نادرة في الشغل اليومي — غالباً بتستخدم bind مع event handlers مش مع constructors. المهم إنك تفهمها عشان تعرف ليه instanceof بيفضل شغال صح حتى بعد كل التلاعب في الـ this.

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

function add(a, b) { console.log(this.tag + " " + (a + b)); }
add.apply({ tag: "المجموع:" }, [3, 4]);
const user = { name: "عمر", hi() { console.log("أهلاً " + this.name); } };
const ready = user.hi.bind(user);
const locked = user.hi.bind(user);
locked.call({ name: "سارة" });

٧ — اختبر نفسك

س1: إيه الفرق الوحيد بين call و apply؟

س2: f.bind(obj) بترجع إيه بالظبط؟

س3: دالة مربوطة بـ bind — جربت تنده عليها بـ call بأوبجكت مختلف. مين بيكسب؟

س4 (سؤال judgment): امتى bind هي الاختيار الصح مش call؟

٨ — التحدي الأخير: اشرحها بصوت عالي

من غير ما تبص على أي حاجة — امشي على كل سطر فيه نداء أو bind، سمّي اللي بيحصل وليه، واقفل بحالة البرنامج:

const dev = { name: "مؤمن", code() { console.log(this.name + " بيكتب كود"); } };
dev.code();
setTimeout(dev.code, 100);
setTimeout(dev.code.bind(dev), 200);

خد بالك: فيه 3 حالات مختلفة هنا — والنص لازم يوضح الفرق بين التانية والتالتة بالذات.

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