هذا المقال يتطلب مستوى متوسط. لو انت لسه بادئ في جافاسكريبت خالص، هتفهم المثال البسيط في الأول عادي، والجزء العلمي هيبقى مرجع ترجعله بعدين.
لو دالتك بتشتغل تمام لمّا تناديها لوحدها، وبتضرب undefined لمّا تحطها جوه setTimeout أو addEventListener، المشكلة مش في الدالة. المشكلة إن قيمة this اترمت وقت النداء. المقال ده هيوريك بالظبط إمتى بتضيع وإزاي تمسكها من غير ما تكسر باقي الكود.
الـ this في جافاسكريبت: قيمة بتتحدد وقت النداء مش وقت الكتابة
المشكلة باختصار
معظم اللغات بتربط الكلمة اللي زي this بالكلاس اللي اتكتبت جواه. جافاسكريبت لأ. هنا this قيمتها بتتحدد حسب إزاي ناديت الدالة، مش حسب فين عرّفتها. نفس الدالة ممكن تديك ثلاث قيم مختلفة لـ this حسب طريقة الاستدعاء. ده اللي بيخلّي مطورين شاطرين يقضّوا ساعات في باج، سببه سطر واحد نقل الدالة من مكان لمكان.
الفكرة بمثال بسيط قبل الكلام العلمي
تخيّل كلمة "أنا" في جملة. لو قلت "أنا جعان"، مين "أنا" دي؟ حسب مين اللي بيتكلم. نفس الكلمة بالظبط، بس معناها بيتغيّر حسب المتحدث اللي نطقها دلوقتي.
الـ this زيها بالظبط. مش اسم ثابت لحاجة معيّنة، ده ضمير بيشير لـ "المتحدث" وقت النداء. و"المتحدث" في جافاسكريبت هو الكائن اللي على شمال النقطة لحظة استدعاء الدالة. غيّر مين اللي بينده، هيتغيّر معنى this.
الشرح العلمي: قيمة this بتتحدد من موقع الاستدعاء
القاعدة الدقيقة: قيمة this بتتحسب من call-site، يعني الشكل اللي اتنادت بيه الدالة فعليًا. فيه أربع طرق ربط، بالأولوية دي:
- ربط عبر new: لو ناديت الدالة بـ
new،thisبتبقى الكائن الجديد. - ربط صريح: لو استخدمت
callأوapplyأوbind، انت اللي بتحددthisبإيدك. - ربط ضمني: لو ناديت الدالة كـ method بالشكل
obj.fn()،thisبتبقىobj. - الربط الافتراضي: لو نادِتها لوحدها
fn()، في الوضع الصارمthisبتبقىundefined، وفي الوضع العادي بتبقى الكائن العام (windowفي المتصفح).
الافتراض هنا إننا بنتكلم عن الدوال العادية. الدوال السهمة (arrow) قصة تانية هنشرحها تحت.