قيمة this في جافا سكريبت تتحدد لحظة الاستدعاء
معظم اللبس حول الكلمة this في جافا سكريبت ينبع من افتراض خاطئ واحد: أن قيمة this تعتمد على المكان الذي عُرِّفت فيه الدالة. الحقيقة غير ذلك تمامًا؛ فقيمة this تتحدد بناءً على الطريقة التي استُدعيت بها الدالة.
تأمّل نفس الدالة وهي تُستدعى بطريقتين مختلفتين:
نفس الدالة، لكن قيمة this مختلفة. في الاستدعاء الأول، كلمة user موجودة قبل النقطة، فتكون this هي user. أما في الاستدعاء الثاني، فلا يوجد شيء قبل النقطة، ولذلك تصبح this هي undefined. الدالة نفسها لم تتغير — ما تغيّر هو موقع الاستدعاء.
احفظ هذه الفكرة جيدًا: لمعرفة قيمة this، انظر إلى طريقة الاستدعاء، لا إلى تعريف الدالة.
قواعد ربط this الأربع في جافا سكريبت
هناك أربع طرق فقط تُحدَّد بها قيمة this. وأي سؤال يخطر ببالك تقريبًا حول this تكون إجابته في تحديد أي من هذه القواعد تنطبق على الحالة.
1. استدعاء كتابع لكائن: obj.fn()
عندما تُستدعى الدالة كخاصية من خصائص كائن معيّن، فإن this تشير إلى ذلك الكائن:
ما يقع قبل النقطة مباشرةً هو الذي يصبح this. بهذه البساطة.
2. استدعاء عادي: fn()
عند استدعاء الدالة دون أي كائن يسبقها، تكون قيمة this هي undefined في الوضع الصارم (strict mode) — وهو الوضع المُفعَّل تلقائيًا داخل الموديولات والكلاسات — أو الكائن العام (global object) في الوضع المتساهل (sloppy mode):
من هنا تأتي أخطاء "this is undefined" الشهيرة. فبمجرد أن تنتزع دالة من كائنها، يتحوّل استدعاء الـ method إلى مجرد استدعاء عادي:
لا يوجد counter. قبل الاستدعاء، فلا يحصل أي ربط. الدالة لا تتذكر الكائن الذي جاءت منه.
3. الربط الصريح: .call() و .apply() و .bind()
تقدر تفرض قيمة this اللي تبيها بنفسك:
.call و .apply كلاهما يستدعي الدالة مباشرة، والفرق الوحيد بينهما هو طريقة تمرير الوسائط. أما .bind فيرجّع لك دالة جديدة مع تثبيت قيمة this بشكل دائم — وهذا مفيد جداً مع الـ callbacks.
4. الاستدعاء باستخدام new: new Fn()
لمّا تستدعي دالة بكلمة new، يتم إنشاء كائن جديد وربط this به تلقائياً:
الـ Classes تعتمد على هذه الآلية تحت الغطاء، وسنتعرّف عليها في فصل لاحق.
لا يوجد this خاص بالدوال السهمية (arrow functions)
الدوال السهمية تكسر القواعد السابقة، وهذا مقصود. فهي لا تربط this إطلاقًا، بل ترثه من النطاق المحيط بها، ويُثبَّت عند لحظة تعريف الدالة السهمية:
الدالة السهمية في أعلى مستوى من الموديول تلتقط this الخاص بالموديول، وقيمته undefined. وحتى لو استدعيناها بالصيغة user.arrow()، فإنها ترفض إعادة ربط this.
قد يبدو هذا خللًا للوهلة الأولى، لكنه في الحقيقة جوهر الفكرة. فالدوال السهمية تتألق عند استخدامها داخل الميثودات، حيث نريد الاحتفاظ بقيمة this الخارجية:
الدالة السهمية داخل setInterval ترث this من start، والأخيرة استُدعيت عبر timer.start(). لهذا يعمل this.seconds كما هو متوقع. لو استخدمنا دالة عادية function في نفس الموضع، لحصلت على this خاص بها (أيًّا كان ما يمرّره setInterval)، وانكسر كل شيء.
قاعدة عملية: استخدم الدوال السهمية للـ callbacks داخل الميثودات، واستخدم الدوال العادية للميثودات نفسها.
فخ الـ callback الكلاسيكي
هذه أشهر طريقة يضيع فيها this في جافا سكريبت. تمرّر ميثود كـ callback، فيفقد ارتباطه (binding):
setTimeout يستدعي الدالة كاستدعاء عادي، وليس بصيغة c.increment(). عندك ثلاث طرق لحل المشكلة:
الطرق الثلاث تؤدي الغرض، لكن التغليف بـ arrow function عادةً هو الأوضح.
قيمة this في النطاق الأعلى
قيمة this في النطاق الأعلى تختلف حسب البيئة التي يعمل فيها الكود:
- سكريبت في المتصفح (غير وحدة):
thisيساويwindow. - وحدة ES module (وهذا ينطبق على معظم الكود الحديث المُجمَّع):
thisيكونundefined. - وحدة CommonJS في Node.js:
thisيساويmodule.exports.
وإذا أردت مرجعًا موثوقًا للكائن العام يعمل في جميع البيئات، استخدم globalThis:
من الناحية العملية، يُفضَّل تجنّب الاعتماد على this في المستوى الأعلى. إذا كنت فعلًا بحاجة إلى الكائن العام، استخدم globalThis، وفيما عدا ذلك مرِّر القيم بشكل صريح بين دوالك.
شجرة قرار سريعة لفهم this في جافا سكريبت
حين تصادف this ولا تعرف إلى ماذا سيشير، طبّق هذه الخطوات بالترتيب:
- هل الدالة سهمية (arrow function)؟ إذًا
thisهو نفسه في النطاق المحيط بها، وتجاهل موقع الاستدعاء تمامًا. - هل استُدعيت باستخدام
new؟ إذًاthisهو الكائن الجديد. - هل استُدعيت عبر
.callأو.applyأو كدالة مربوطة بـbind؟ إذًاthisهو ما تم تمريره. - هل استُدعيت بصيغة
obj.method()؟ إذًاthisهوobj. - هل استُدعيت كدالة عادية
fn()؟ إذًاthisيكونundefinedفي الوضع الصارم (strict mode).
هذا السُّلَّم، وبهذا الترتيب تحديدًا، يحسم كل الحالات.
الخطوة التالية: الدوال من المرتبة الأعلى
بعد أن أصبحت الكلمة this في جافا سكريبت واضحة لم تعد لغزًا، صرت جاهزًا للنمط الذي يمنح جافا سكريبت قوتها التعبيرية: تمرير الدوال كقيم. في الدرس القادم سنتناول الدوال من المرتبة الأعلى (higher-order functions) — أي الدوال التي تستقبل دوالًا أخرى أو تُعيدها — وكيف تقف خلف توابع المصفوفات، ومعالِجات الأحداث، ومعظم أكواد جافا سكريبت التي تكتبها في المشاريع الحقيقية.
الأسئلة الشائعة
إلى ماذا تشير this في جافا سكريبت؟
this في جافا سكريبت؟تشير this إلى الكائن الذي استُدعيت عليه الدالة — وليس المكان الذي عُرِّفت فيه. في user.greet() تكون this هي user، أما عند استدعاء greet() لوحدها فتكون this هي undefined في وضع strict mode (أو الكائن العام في الوضع العادي). القاعدة تعتمد على موضع الاستدعاء (call site)، لا على موضع التعريف.
لماذا تكون this بقيمة undefined داخل الدالة عندي؟
this بقيمة undefined داخل الدالة عندي؟على الأغلب فصلتَ الميثود عن الكائن واستدعيتَها بشكل مستقل، أو مرّرتها كـ callback. مثلاً const fn = user.greet; fn(); يُفقِد الربط لأنه لا يوجد كائن على يسار النقطة عند الاستدعاء. الحل: استخدم .bind(user)، أو غلّفها بدالة سهمية، أو استدعِها مباشرة بصيغة user.greet().
ما الفرق في سلوك this داخل الدوال السهمية؟
this داخل الدوال السهمية؟الدوال السهمية لا تملك this خاصة بها، بل ترث قيمتها من النطاق المحيط بها لحظة تعريفها. لذلك تُعدّ مثالية لاستخدامها كـ callbacks داخل الميثودز عندما تريد الاحتفاظ بـ this الخارجية. كما يعني ذلك أن .call() و.apply() و.bind() لا تستطيع تغيير قيمة this في الدالة السهمية.
ما قيمة this في المستوى الأعلى من السكريبت؟
this في المستوى الأعلى من السكريبت؟في سكريبت عادي داخل المتصفح تكون this في المستوى الأعلى هي كائن window. أما في ES modules فقيمتها undefined، وفي وحدات Node.js من نوع CommonJS تكون module.exports. المرجع الموحّد عبر جميع البيئات هو globalThis، وهو يشير دائماً إلى الكائن العام.