Menu
العربية

الوراثة في JavaScript: extends و super وإعادة تعريف الدوال

تعرّف على كيفية عمل الوراثة في أصناف JavaScript: استخدام extends، واستدعاء super، وإعادة تعريف الدوال، ومتى يكون التركيب (composition) خياراً أفضل.

البناء على كلاس موجود

تتيح لك الوراثة في javascript أن تبدأ من مخطط كلاس آخر وتوسّعه. تحصل على جميع الحقول والدوال الخاصة بالكلاس الأب مجاناً، ومن ثَمّ تضيف أو تعدّل ما تحتاجه:

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

Dog extends Animal تعني ببساطة: "الكلب حيوان، مع إضافات بسيطة." الكائن rex لا يملك دالة speak خاصة به، لكن عملية البحث تصعد في السلسلة حتى تصل إلى Animal الذي يحتويها. هذا الصعود في السلسلة هو جوهر الآلية كلها — إنها ببساطة prototypal inheritance بصياغة أجمل.

استخدام super داخل الـ constructor

أي subclass في javascript يعرّف constructor خاص به محكوم بقاعدة صارمة واحدة: يجب استدعاء super(...) قبل أي تعامل مع this. استدعاء super يشغّل constructor الأب، وهو المسؤول فعلياً عن إنشاء الكائن وتهيئته:

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

إذا تجاهلت السطر super(name)، فسيرمي لك المحرّك خطأ ReferenceError بمجرد محاولتك قراءة this أو الكتابة إليه. ببساطة، لن يسمح لك المحرّك باستخدام this قبل أن ينتهي الأب من عمله.

وإذا لم تُعرِّف الفئة الابنة (subclass) باني (constructor) خاصًا بها، فإن JavaScript يُولِّد لها واحدًا تلقائيًا يُمرِّر كل الوسائط إلى super، ولهذا لا تحتاج لكتابته بنفسك إلا حين تُضيف حقولًا جديدة أو تحتاج لإعدادات إضافية.

إعادة تعريف الدوال في javascript

يمكن لأي فئة ابنة أن تُعيد تعريف (override) أي دالة ورثتها، والدالة الأقرب في سلسلة الوراثة هي التي تفوز:

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

لا يوجد سحر هنا — عندما تستدعي speak() على كائن من النوع Dog، يبحث المحرك عن speak داخل الكائن نفسه أولًا، ثم في Dog.prototype، فيجدها هناك ويتوقف. لن يصل أبدًا إلى Animal.prototype.

التوسيع بدل الاستبدال: super.method()

في بعض الأحيان لا تريد أن تستبدل دالة الأب بالكامل — بل تريد أن تضيف إليها. هنا يأتي دور super.method(...)، إذ يتيح لك استدعاء نسخة الأب من داخل الدالة التي أعدت تعريفها:

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

هنا تظهر الفائدة الحقيقية من الوراثة في javascript: الكلاس الابن يعيد استخدام منطق الكلاس الأب بدلاً من تكراره. لو تغيّرت Animal.describe لاحقاً، فإن Dog.describe ستلتقط التغيير تلقائياً دون أي جهد إضافي.

الكلمة المفتاحية super لا تقتصر على الـ constructor، بل تعمل داخل أي ميثود. وهي تشير دائماً إلى نسخة الكلاس الأب من الشيء الذي تستدعيه.

instanceof وسلسلة الـ prototype

يتحقق instanceof مما إذا كانت سلسلة الـ prototype الخاصة بالكائن تحتوي على كلاس معيّن. وكل كائن من الكلاس الابن يُعتبر أيضاً كائناً من كلاسات الآباء:

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

الأربعة كلهم true. سلسلة الوراثة تسير هكذا: Puppy -> Dog -> Animal -> Object، وinstanceof يمشي على هذه السلسلة كاملةً. مفيد للتحقق من النوع، لكن عمليًا ستحتاجه أقل مما تتوقع، لأن معظم الكود يكتفي باستدعاء الدوال ويترك تعدد الأشكال (polymorphism) يتكفل بالباقي.

مثال أوسع قليلًا على الوراثة في javascript

نمط شائع جدًا: كلاس أساسي يحمل المنطق المشترك، ومعه كلاسان فرعيان (subclasses) يخصّصان هذا المنطق لحالتهما.

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

لاحظ أن describe موجودة في Shape ولا حاجة لإعادة كتابتها أبدًا — فهي ببساطة تستدعي this.area()، والتي تُحَلّ في وقت التشغيل إلى الدالة الصحيحة في الـ subclass المناسب. هذا هو جوهر تعدد الأشكال (Polymorphism): نفس موضع الاستدعاء، لكن السلوك يختلف حسب الكائن الفعلي.

الوراثة مقابل التركيب في javascript

الوراثة مغرية لأنها تمنحك شعورًا بالإنتاجية — سطر واحد يجلب لك كومة من الدوال جاهزة. لكنها لا تصمد جيدًا مع الوقت حين تكبر التسلسلات الهرمية.

قاعدة عملية: استخدم extends حين تكون العلاقة واضحة من نوع "X هو Y"، ويكون الـ subclass فعلًا يشارك معظم سلوك الكلاس الأب. أما إذا كنت تلجأ إلى الوراثة لمجرد مشاركة دالة مساعدة أو اثنتين، فالأفضل هو التركيب (Composition) — امنح الكلاس حقلًا يحتفظ بالكائن المساعد:

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

شجرات الوراثة العميقة مثل (Animal -> Mammal -> Dog -> WorkingDog -> PoliceDog) تبدو أنيقة في المخططات، لكنها تتحول إلى كابوس حقيقي في الكود — أي تعديل قريب من الجذر ينتشر بشكل غير متوقع عبر كل الأبناء والأحفاد. معظم المشاريع الصحية تبقى بعمق مستوى أو اثنين فقط، وتعتمد على التركيب (Composition) لبقية الحالات.

الخطوة التالية: الأعضاء الساكنون (Static Members)

كل ما ناقشناه في هذا الدرس يعيش على الكائنات (instances) — دوال تستدعيها عبر new Thing().something(). لكن أحياناً تحتاج إلى دوال أو بيانات تخص الصنف (class) نفسه، لا أي كائن بعينه. هنا يأتي دور static، وهو موضوعنا القادم.

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

كيف تعمل الوراثة في JavaScript؟

يمكن لصنف أن يرث من صنف آخر باستخدام extends. يحصل الصنف الفرعي (subclass) على كل دوال وخصائص الصنف الأب، ويستطيع إضافة دوال جديدة أو إعادة تعريف الموجودة. خلف الكواليس، يربط JavaScript الـ prototype الخاص بالصنف الفرعي بـ prototype الصنف الأب، فيتم البحث عن الدوال في سلسلة الـ prototype تلقائياً.

ما وظيفة super في JavaScript؟

الاستدعاء super(...) يُشغّل الـ constructor الخاص بالصنف الأب، ولا بد من استدعائه قبل استخدام this داخل constructor الصنف الفرعي. أما super.method(...) فيستدعي نسخة الدالة الموجودة في الصنف الأب، وهذا ما يتيح لك توسيع السلوك بدلاً من استبداله بالكامل.

هل أستخدم الوراثة أم التركيب (composition) في JavaScript؟

استخدم الوراثة عندما تكون العلاقة فعلاً علاقة "هو نوع من" (is-a)، ويشترك الصنف الفرعي في معظم سلوك الصنف الأب. أما عندما تريد مجرد مشاركة وظيفة معينة، فالأفضل هو التركيب — أي أن يحتوي كائن على كائنات أخرى. التسلسلات العميقة من الأصناف تصبح صعبة الصيانة مع الوقت، ولذلك تجد أغلب المشاريع الحقيقية لا تتجاوز مستوى أو مستويين من الوراثة.

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

ابدأ الآن