Menu
العربية

الكلمة this في جافا سكريبت: قواعد الربط وأخطاء شائعة

كيف تعمل this فعلياً في جافا سكريبت — قواعد الربط الأربعة، لماذا تتصرف الدوال السهمية بشكل مختلف، وكيف تتجنّب فخ 'this is undefined' الشهير.

قيمة this في جافا سكريبت تتحدد لحظة الاستدعاء

معظم اللبس حول الكلمة this في جافا سكريبت ينبع من افتراض خاطئ واحد: أن قيمة this تعتمد على المكان الذي عُرِّفت فيه الدالة. الحقيقة غير ذلك تمامًا؛ فقيمة this تتحدد بناءً على الطريقة التي استُدعيت بها الدالة.

تأمّل نفس الدالة وهي تُستدعى بطريقتين مختلفتين:

index.js
Output
Click Run to see the output here.

نفس الدالة، لكن قيمة this مختلفة. في الاستدعاء الأول، كلمة user موجودة قبل النقطة، فتكون this هي user. أما في الاستدعاء الثاني، فلا يوجد شيء قبل النقطة، ولذلك تصبح this هي undefined. الدالة نفسها لم تتغير — ما تغيّر هو موقع الاستدعاء.

احفظ هذه الفكرة جيدًا: لمعرفة قيمة this، انظر إلى طريقة الاستدعاء، لا إلى تعريف الدالة.

قواعد ربط this الأربع في جافا سكريبت

هناك أربع طرق فقط تُحدَّد بها قيمة this. وأي سؤال يخطر ببالك تقريبًا حول this تكون إجابته في تحديد أي من هذه القواعد تنطبق على الحالة.

1. استدعاء كتابع لكائن: obj.fn()

عندما تُستدعى الدالة كخاصية من خصائص كائن معيّن، فإن this تشير إلى ذلك الكائن:

index.js
Output
Click Run to see the output here.

ما يقع قبل النقطة مباشرةً هو الذي يصبح this. بهذه البساطة.

2. استدعاء عادي: fn()

عند استدعاء الدالة دون أي كائن يسبقها، تكون قيمة this هي undefined في الوضع الصارم (strict mode) — وهو الوضع المُفعَّل تلقائيًا داخل الموديولات والكلاسات — أو الكائن العام (global object) في الوضع المتساهل (sloppy mode):

index.js
Output
Click Run to see the output here.

من هنا تأتي أخطاء "this is undefined" الشهيرة. فبمجرد أن تنتزع دالة من كائنها، يتحوّل استدعاء الـ method إلى مجرد استدعاء عادي:

index.js
Output
Click Run to see the output here.

لا يوجد counter. قبل الاستدعاء، فلا يحصل أي ربط. الدالة لا تتذكر الكائن الذي جاءت منه.

3. الربط الصريح: .call() و .apply() و .bind()

تقدر تفرض قيمة this اللي تبيها بنفسك:

index.js
Output
Click Run to see the output here.

.call و .apply كلاهما يستدعي الدالة مباشرة، والفرق الوحيد بينهما هو طريقة تمرير الوسائط. أما .bind فيرجّع لك دالة جديدة مع تثبيت قيمة this بشكل دائم — وهذا مفيد جداً مع الـ callbacks.

4. الاستدعاء باستخدام new: new Fn()

لمّا تستدعي دالة بكلمة new، يتم إنشاء كائن جديد وربط this به تلقائياً:

index.js
Output
Click Run to see the output here.

الـ Classes تعتمد على هذه الآلية تحت الغطاء، وسنتعرّف عليها في فصل لاحق.

لا يوجد this خاص بالدوال السهمية (arrow functions)

الدوال السهمية تكسر القواعد السابقة، وهذا مقصود. فهي لا تربط this إطلاقًا، بل ترثه من النطاق المحيط بها، ويُثبَّت عند لحظة تعريف الدالة السهمية:

index.js
Output
Click Run to see the output here.

الدالة السهمية في أعلى مستوى من الموديول تلتقط this الخاص بالموديول، وقيمته undefined. وحتى لو استدعيناها بالصيغة user.arrow()، فإنها ترفض إعادة ربط this.

قد يبدو هذا خللًا للوهلة الأولى، لكنه في الحقيقة جوهر الفكرة. فالدوال السهمية تتألق عند استخدامها داخل الميثودات، حيث نريد الاحتفاظ بقيمة this الخارجية:

index.js
Output
Click Run to see the output here.

الدالة السهمية داخل setInterval ترث this من start، والأخيرة استُدعيت عبر timer.start(). لهذا يعمل this.seconds كما هو متوقع. لو استخدمنا دالة عادية function في نفس الموضع، لحصلت على this خاص بها (أيًّا كان ما يمرّره setInterval)، وانكسر كل شيء.

قاعدة عملية: استخدم الدوال السهمية للـ callbacks داخل الميثودات، واستخدم الدوال العادية للميثودات نفسها.

فخ الـ callback الكلاسيكي

هذه أشهر طريقة يضيع فيها this في جافا سكريبت. تمرّر ميثود كـ callback، فيفقد ارتباطه (binding):

index.js
Output
Click Run to see the output here.

setTimeout يستدعي الدالة كاستدعاء عادي، وليس بصيغة c.increment(). عندك ثلاث طرق لحل المشكلة:

index.js
Output
Click Run to see the output here.

الطرق الثلاث تؤدي الغرض، لكن التغليف بـ arrow function عادةً هو الأوضح.

قيمة this في النطاق الأعلى

قيمة this في النطاق الأعلى تختلف حسب البيئة التي يعمل فيها الكود:

  • سكريبت في المتصفح (غير وحدة): this يساوي window.
  • وحدة ES module (وهذا ينطبق على معظم الكود الحديث المُجمَّع): this يكون undefined.
  • وحدة CommonJS في Node.js: this يساوي module.exports.

وإذا أردت مرجعًا موثوقًا للكائن العام يعمل في جميع البيئات، استخدم globalThis:

index.js
Output
Click Run to see the output here.

من الناحية العملية، يُفضَّل تجنّب الاعتماد على this في المستوى الأعلى. إذا كنت فعلًا بحاجة إلى الكائن العام، استخدم globalThis، وفيما عدا ذلك مرِّر القيم بشكل صريح بين دوالك.

شجرة قرار سريعة لفهم this في جافا سكريبت

حين تصادف this ولا تعرف إلى ماذا سيشير، طبّق هذه الخطوات بالترتيب:

  1. هل الدالة سهمية (arrow function)؟ إذًا this هو نفسه في النطاق المحيط بها، وتجاهل موقع الاستدعاء تمامًا.
  2. هل استُدعيت باستخدام new؟ إذًا this هو الكائن الجديد.
  3. هل استُدعيت عبر .call أو .apply أو كدالة مربوطة بـ bind؟ إذًا this هو ما تم تمريره.
  4. هل استُدعيت بصيغة obj.method()؟ إذًا this هو obj.
  5. هل استُدعيت كدالة عادية fn()؟ إذًا this يكون undefined في الوضع الصارم (strict mode).

هذا السُّلَّم، وبهذا الترتيب تحديدًا، يحسم كل الحالات.

الخطوة التالية: الدوال من المرتبة الأعلى

بعد أن أصبحت الكلمة this في جافا سكريبت واضحة لم تعد لغزًا، صرت جاهزًا للنمط الذي يمنح جافا سكريبت قوتها التعبيرية: تمرير الدوال كقيم. في الدرس القادم سنتناول الدوال من المرتبة الأعلى (higher-order functions) — أي الدوال التي تستقبل دوالًا أخرى أو تُعيدها — وكيف تقف خلف توابع المصفوفات، ومعالِجات الأحداث، ومعظم أكواد جافا سكريبت التي تكتبها في المشاريع الحقيقية.

الأسئلة الشائعة

إلى ماذا تشير this في جافا سكريبت؟

تشير this إلى الكائن الذي استُدعيت عليه الدالة — وليس المكان الذي عُرِّفت فيه. في user.greet() تكون this هي user، أما عند استدعاء greet() لوحدها فتكون this هي undefined في وضع strict mode (أو الكائن العام في الوضع العادي). القاعدة تعتمد على موضع الاستدعاء (call site)، لا على موضع التعريف.

لماذا تكون this بقيمة undefined داخل الدالة عندي؟

على الأغلب فصلتَ الميثود عن الكائن واستدعيتَها بشكل مستقل، أو مرّرتها كـ callback. مثلاً const fn = user.greet; fn(); يُفقِد الربط لأنه لا يوجد كائن على يسار النقطة عند الاستدعاء. الحل: استخدم .bind(user)، أو غلّفها بدالة سهمية، أو استدعِها مباشرة بصيغة user.greet().

ما الفرق في سلوك this داخل الدوال السهمية؟

الدوال السهمية لا تملك this خاصة بها، بل ترث قيمتها من النطاق المحيط بها لحظة تعريفها. لذلك تُعدّ مثالية لاستخدامها كـ callbacks داخل الميثودز عندما تريد الاحتفاظ بـ this الخارجية. كما يعني ذلك أن .call() و.apply() و.bind() لا تستطيع تغيير قيمة this في الدالة السهمية.

ما قيمة this في المستوى الأعلى من السكريبت؟

في سكريبت عادي داخل المتصفح تكون this في المستوى الأعلى هي كائن window. أما في ES modules فقيمتها undefined، وفي وحدات Node.js من نوع CommonJS تكون module.exports. المرجع الموحّد عبر جميع البيئات هو globalThis، وهو يشير دائماً إلى الكائن العام.

تعلّم البرمجة مع Coddy

ابدأ الآن