الأعضاء الساكنة تنتمي إلى الصنف نفسه لا إلى الكائن
معظم الدوال التي تكتبها داخل الصنف (class) تعمل على كائن محدد — مستخدم بعينه، أو دائرة بعينها. أما الدوال الساكنة في javascript فهي قصة مختلفة؛ إذ تنتمي إلى الصنف ذاته، وتستدعيها باسم الصنف مباشرةً لا عبر كائن مُنشأ منه.
double تعيش على MathUtils نفسها، وليس على النسخ (instances) التي تُنشأ منها. إنشاء نسخة لن يُفيدك هنا؛ فـ m.double قيمتها undefined. وهذا عكس الدوال العادية تمامًا، لأنها تعيش على النسخ (تقنيًا على الـ prototype)، والصنف نفسه لا يراها.
تصوّر الأمر هكذا: الكلمة المفتاحية class تُنشئ شيئين في الحقيقة — مجموعة من دوال النسخة (تستخدمها عبر new MathUtils())، ومجموعة من الدوال الساكنة (تستدعيها من MathUtils مباشرة). وكلمة static هي التي تُحدّد في أيّ من السلّتين يستقر العضو.
الاستخدام الكلاسيكي: دوال المصنع (Factory Methods)
أكثر سبب شائع لكتابة دالة ساكنة هو توفير باني بديل (alternative constructor). فالـ constructor الأصلي يأخذ وسائطه المحدّدة، لكنك كثيرًا ما تحتاج طُرقًا أخرى لبناء الكائن — من JSON، أو من صفّ في قاعدة بيانات، أو من رابط URL.
الدالة fromJSON لا تستخدم كائن مستخدم — بل تُنتج واحدًا. وهذه بالضبط الحالة التي تناسب فيها الدوال الساكنة. البديل هو كتابة دالة حرة مستقلة باسم parseUser، لكن إبقاءها داخل الصنف يجمّع السلوك المترابط في مكان واحد ويجعل الغرض منها واضحًا عند الاستدعاء.
الخصائص الساكنة (static properties)
يمكنك كذلك إرفاق بيانات بالصنف نفسه مباشرة:
Circle.PI قيمة مشتركة بين كل الدوائر، فهي ثابت معرَّف على الصنف نفسه ولا تُنسخ داخل كل كائن. وعند استخدامها داخل دوال الكائنات (instance methods)، تشير إليها عبر اسم الصنف (Circle.PI) وليس عبر this.
خصائص static مفيدة جدًا لحفظ الإعدادات، أو الكاش المشترك بين الكائنات، أو العدادات، أو الثوابت التي تخص الصنف ككل.
قيمة this داخل الدوال الساكنة في javascript تشير إلى الصنف
داخل الدوال العادية، تشير this إلى الكائن (instance). أما داخل الدوال الساكنة (static methods)، فإن this تشير إلى الصنف نفسه:
this.count داخل increment تعني Counter.count. قد يبدو الأمر غريبًا في البداية، لكنه بالضبط ما يجعل وراثة الـ static methods تعمل — فـ this يشير إلى الكلاس الذي استدعيت منه الدالة، وليس الكلاس الذي عرّفها.
وراثة الـ static methods في جافاسكربت
الدوال الساكنة في JavaScript تُورَّث إلى الكلاسات الفرعية. وبما أن this يشير إلى الكلاس الذي استُدعيت منه الدالة، فإن دوال المصنع (factory methods) تُنتج الكلاس الفرعي الصحيح تلقائيًا:
Animal.create يستخدم new this(name). عند استدعاء Dog.create("Rex")، تكون قيمة this هي Dog، وبالتالي فإن new this(name) ينشئ كائناً من النوع Dog. أما لو كتبنا new Animal(name) بدلاً من ذلك، فسينتج عنه دائماً كائن Animal فقط، مما يُفسد الفكرة بالكامل. هذا هو السبب الرئيسي الذي يجعل this داخل الدوال الساكنة يشير إلى الصنف (الكلاس) نفسه.
الفرق بين static و instance: مثال عملي للمقارنة
لنأخذ نفس المنطق ونوزّعه على الأسلوبين لنرى الفرق بوضوح:
كلا الأسلوبين يُنفّذان نفس العملية الحسابية. الفرق أن نسخة ال instance method تقرأ البيانات من this.celsius، بينما تستقبل نسخة ال static method المدخلات عبر وسيط (argument). استخدم ال instance method حين تكون العملية "شيئاً يفعله هذا الكائن بذاته"، واستخدم ال static method حين تكون "شيئاً تعرف الكلاس كيف يحسبه انطلاقاً من مدخلات تُعطى له".
استخدام static blocks للتهيئة
أحياناً لا تكفي عبارة واحدة لتهيئة static property، فقد تحتاج إلى حلقة أو شرط أو عدة قيم يعتمد بعضها على بعض. هنا يأتي دور ال static blocks في javascript:
كتلة static { ... } تُنفَّذ مرة واحدة فقط، وقت تعريف الكلاس. وبداخلها، this يشير إلى الكلاس نفسه. استخدمها عندما تحتاج إلى إعداد متعدد الخطوات؛ أما إذا كان الأمر مجرد إسناد واحد، فالأفضل أن تكتفي بحقل static عادي لأنه أوضح.
الأعضاء الساكنة الخاصة (Private Static)
يمكن أيضًا جعل الحقول الساكنة خاصة عبر استخدام البادئة #، وعندها لن يُسمح بالوصول إليها إلا من داخل الكلاس:
#nextId محبوس جوّا الكلاس ومفيش طريقة للوصول ليه من بره. الكود الخارجي يقدر يستدعي IdGenerator.next() بس، لكن ميقدرش يلمس العدّاد أو يعمله reset. هنتكلم عن الحقول الخاصة (private fields) في صفحة مستقلة قريب، لكن المهم دلوقتي إنك تعرف إن الجمع بين static و# شغّال تمام.
إمتى ما تستخدمش static؟
الدوال الساكنة في javascript طريقة لطيفة لتجميع الدوال المساعدة مع بعضها، لكن ده مش مبرّر إنك تحوّل كل utility عندك لكلاس. لو عندك ملف مليان دوال مستقلة، صدّر الدوال نفسها على طول — مش لازم تلفّها في كلاس كل أعضائه static عشان تعمل لها namespace وبس. الـ module أصلاً بيقوم بالمهمة دي، وبشكل أنظف كمان.
استخدم static في الحالات دي:
- لما الدالة تنتمي فعلاً للكلاس (زي factory method، أو converter، أو validator خاص بالنوع ده).
- لما تحتاج حالة (state) مشتركة بين كل الـ instances التابعة للكلاس.
- لما يكون فيه subclass محتاج يرث السلوك ده أو يعيد تعريفه.
غير كده، الدالة العادية هي الخيار الأبسط.
التالي: الحقول الخاصة (Private Fields)
شُفت #nextId بسرعة فوق — دي صيغة الحقول الخاصة في JavaScript. بتشتغل مع الأعضاء العادية والـ static على حد سواء، وهي الطريقة الحديثة لإخفاء تفاصيل التنفيذ جوّا الكلاس. ده اللي هنتكلم عنه في الجزء الجاي.
الأسئلة الشائعة
ما هي الدالة الساكنة static method في JavaScript؟
الدالة الساكنة تنتمي إلى الصنف (class) نفسه وليس إلى كائناته. تُعرَّف باستخدام الكلمة المفتاحية static وتُستدعى مباشرة على الصنف هكذا: MyClass.doThing(). الكائنات المُنشأة من الصنف لا تستطيع استدعاءها عبر this.doThing()، الصنف وحده من يملكها.
متى أستخدم static method بدلاً من instance method؟
استخدم static عندما تكون الدالة مرتبطة بالصنف لكنها لا تحتاج لقراءة أو تعديل بيانات كائن محدد. أمثلة شائعة: دوال المصنع مثل User.fromJSON(...)، ودوال مساعدة utility مثل Math.max، والثوابت التي تنتمي للصنف كـ namespace. أما إذا احتاجت الدالة إلى this للإشارة إلى كائن فعلي، فهي instance method.
هل يمكن للدوال الساكنة الوصول إلى خصائص الكائن instance؟
ليس بشكل مباشر. داخل static method تكون this مشيرة إلى الصنف نفسه وليس إلى كائن منه، لذلك this.name ستقرأ خاصية ساكنة وليس حقل كائن. إذا احتجت لبيانات كائن معيّن، مرّر الكائن كمعامل: static summarize(user) { return user.name; }.
هل تُورَّث الدوال الساكنة في JavaScript؟
نعم. عندما يرث صنف فرعي من صنف أب باستخدام extends، تصبح الدوال الساكنة في الأب متاحة على الصنف الفرعي أيضاً. وداخل الدالة الساكنة تشير this إلى الصنف الذي استدعيتها عليه فعلياً، وهذا ما يجعل دوال المصنع static factory تعمل بشكل صحيح مع الأصناف الفرعية.