✅ ما هي الوراثة؟
الوراثة هي مفهوم في البرمجة الكائنية (OOP) يسمح لك بإنشاء كلاس جديد (الابن) يرث الخصائص والدوال من كلاس آخر (الأب).
🧠 فوائد الوراثة:
- إعادة استخدام الكود (Reuse).
- تقليل التكرار.
- تنظيم العلاقات بين الكائنات.
- تسهيل التوسعة والتعديل.
📘 الأساسيات في JavaScript:
➤ extends
يُستخدم لجعل كلاس يرث من كلاس آخر.
➤ super()
يُستخدم داخل constructor
لاستدعاء دالة البناء (constructor) الخاصة بالكلاس الأب.
🧪 مثال بسيط على الوراثة:
class Employee {
constructor(name) {
this.name = name;
}
work() {
return `${this.name} is working.`;
}
}
class Manager extends Employee {
constructor(name, department) {
super(name); // ضروري لاستدعاء constructor الأب
this.department = department;
}
work() {
return `${this.name} is managing the ${this.department} department.`;
}
}
let manager1 = new Manager("Ahmed", "IT");
console.log(manager1.work()); // Ahmed is managing the IT department.
JavaScriptشرح سريع:
Employee
هي الكلاس الأب وتمثل الموظف العادي.Manager
هي الكلاس الابن وتمثل المدير الذي يرث منEmployee
.- استخدمنا
super(name)
لاستدعاء مُنشئ (constructor) الكلاس الأب. - قمنا بعمل Override للدالة
work
داخلManager
لتعبر عن وظيفة المدير بشكل خاص.
✅ الوصول إلى دوال الأب بدون تعديل (بدون Override):
class Employee {
constructor(name) {
this.name = name;
}
work() {
return `${this.name} is working.`;
}
}
class RegularEmployee extends Employee {
// لا نعيد تعريف work()، سيتم استخدام الدالة من الكلاس الأب
}
let emp1 = new RegularEmployee("Sara");
console.log(emp1.work()); // Sara is working.
JavaScriptشرح:
Employee
: الكلاس الأساسي يمثل موظفًا عاديًا.RegularEmployee
: كلاس يرث منEmployee
لكن لا يضيف أو يغير أي شيء.- الكائن
emp1
عند استدعاءwork()
عليه، سينفذ الدالة الموروثة منEmployee
.
🔄 تعديل دالة (Override Method)
عند استخدام الوراثة، الكلاس الابن يمكنه إعادة تعريف دوال الكلاس الأب ليغير سلوكها حسب الحاجة، وهذا ما يُسمى Override.
مثال توضيحي:
نأخذ الكلاس Employee
ونضيف كلاس Intern
(متدرب) يعيد تعريف work()
ليعبر عن أن المتدرب يتعلم، ليس فقط يعمل.
class Employee {
constructor(name) {
this.name = name;
}
work() {
return `${this.name} is working.`;
}
}
class Intern extends Employee {
work() {
return `${this.name} is learning and assisting.`;
}
}
let intern1 = new Intern("Yousef");
console.log(intern1.work()); // Yousef is learning and assisting.
JavaScriptما حدث هنا:
- الكلاس
Intern
ورث منEmployee
. - أعدنا تعريف (Override) دالة
work()
فيIntern
. - لذلك عند استدعاء
intern1.work()
تم استخدام النسخة الخاصة بـIntern
وليس النسخة الأصلية منEmployee
.
✅ مثال: الوصول إلى دالة الأب من دالة الإبن في الموظفين:
class Employee {
constructor(name) {
this.name = name;
}
work() {
return `${this.name} is working.`;
}
}
class Manager extends Employee {
work() {
return super.work() + ` And also managing the team.`;
}
}
let m = new Manager("Omar");
console.log(m.work()); // Omar is working. And also managing the team.
JavaScript🔍 الشرح:
Manager
يرث منEmployee
.- أعدنا تعريف دالة
work()
داخلManager
. - استخدمنا
super.work()
للوصول إلى دالة الأب (Employee). - أضفنا سلوكًا إضافيًا خاصًا بالمدير بعد نداء دالة الأب.
📌 وراثة الخصائص والدوال:
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
return `Hi, I'm ${this.name}`;
}
}
class Student extends Person {
study() {
return `${this.name} is studying.`;
}
}
let student = new Student("Ahmed");
console.log(student.sayHi()); // Hi, I'm Ahmed
console.log(student.study()); // Ahmed is studying.
JavaScriptفي بعض الأمثلة وجود super()
داخل الكلاس الابن، وأحيانًا لا تُكتب — والسبب يعود إلى وجود أو عدم وجود constructor داخل الكلاس الابن.
✅ القاعدة الذهبية:
إذا كان الكلاس الابن يحتوي على
constructor
، فيجب استدعاءsuper()
داخل هذا الـ constructor قبل استخدامthis
.
📌 الحالة 1: كلاس الابن لا يحتوي على constructor
في هذه الحالة، JavaScript تضيف constructor ضمنيًا وتستدعي super()
تلقائيًا، لذلك لا تحتاج لكتابته يدويًا.
✅ مثال:
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
// لا يوجد constructor هنا
}
let c = new Cat("Kitty");
console.log(c.name); // Kitty
JavaScript✅ هنا Cat
يرث من Animal
، وJS تنشئ constructor تلقائيًا كالتالي:
constructor(...args) {
super(...args);
}
JavaScript📌 الحالة 2: كلاس الابن يحتوي على constructor خاص به
هنا مطلوب كتابة super()
يدويًا لأنه لا يتم إضافته تلقائيًا، ويجب أن تكتبه قبل استخدام this
.
❌ بدون super:
class Cat extends Animal {
constructor(name) {
this.name = name; // ❌ خطأ: يجب استدعاء super أولاً
}
}
JavaScript✅ مع super:
class Cat extends Animal {
constructor(name) {
super(name); // ✅ استدعاء constructor الأب
this.type = "Cute Cat";
}
}
JavaScript🟢 إذا لم تكن بحاجة لتعريف constructor
خاص في الكلاس الابن — يمكنك ببساطة عدم كتابته إطلاقًا، وستقوم JavaScript تلقائيًا بإنشاء واحد داخليًا يقوم بما يلي:
🧠 لماذا يجب استخدام super()
؟
لأن JavaScript تحتاج إلى تهيئة الجزء الوراثي (الأب) أولًا قبل أن تسمح باستخدام this
في الكلاس الابن.
🎯 متى لا تحتاج إلى كتابة constructor؟
- إذا لم تكن تضيف خصائص جديدة داخل الكلاس الابن.
- أو لا تقوم بأي تهيئة إضافية غير التي يرثها من الأب.
✅ مثال توضيحي بدون constructor:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
// لا يوجد constructor هنا
}
let dog1 = new Dog("Rex");
console.log(dog1.speak()); // Rex makes a sound
JavaScript✔️ كل شيء يعمل، لأن JavaScript أضافت constructor ضمنيًا فيه super(name)
.
🟡 لكن… إذا أردت تخصيص خصائص في الابن، تحتاج إلى كتابة constructor:
class Dog extends Animal {
constructor(name, breed) {
super(name); // ✅ لازم نبدأ بـ super()
this.breed = breed; // خاصية جديدة للابن
}
}
JavaScript✳️ خلاصة سريعة:
الحالة | تكتب constructor؟ | تكتب super؟ |
---|---|---|
فقط تورث خصائص من الأب | ❌ لا | ❌ لا |
تريد إضافة خصائص جديدة في الإبن | ✅ نعم | ✅ نعم |
تعدل على constructor الأب | ✅ نعم | ✅ نعم |
📝 خلاصة:
الحالة | هل يجب كتابة super() ؟ |
---|---|
لا يوجد constructor في الإبن | ❌ لا، JavaScript تفعل ذلك تلقائيًا |
يوجد constructor في الإبن | ✅ نعم، وإلا سيحدث خطأ |
❗ ملاحظات مهمة:
المعلومة | التوضيح |
---|---|
يجب استخدام super() داخل constructor الابن قبل استخدام this | ✅ إجباري |
يمكن للكلاس الابن استخدام كل خصائص ودوال الأب | ✅ |
يمكن تعديل (Override) أي دالة في الأب | ✅ |
يمكن وراثة الوراثة (سلسلة inheritance) | ✅ |
🔗 وراثة متعددة المستويات (Multi-level Inheritance):
class A {
hello() {
return "Hello from A";
}
}
class B extends A {
hi() {
return "Hi from B";
}
}
class C extends B {
greet() {
return "Greetings from C";
}
}
let obj = new C();
console.log(obj.hello()); // Hello from A
console.log(obj.hi()); // Hi from B
console.log(obj.greet()); // Greetings from C
JavaScript🚫 الوراثة المتعددة (Multiple Inheritance) غير مدعومة بشكل مباشر في JavaScript
لا يمكن أن يرث كلاس من أكثر من كلاس مباشرة:
✅ خلاصة الوراثة في JavaScript
المفهوم | الوظيفة |
---|---|
extends | لربط كلاس بابن يرث من كلاس آخر |
super() | لاستدعاء constructor أو دوال الأب |
override method | تعديل دالة موروثة داخل الإبن |
super.method() | استدعاء دالة من الأب داخل الإبن |
الوراثة المتعددة | غير مدعومة مباشرة، يمكن استخدام mixins |
✅ ما هي super
؟
super
هي كلمة مفتاحية (keyword) تُستخدم داخل الكلاس الابن للإشارة إلى الكلاس الأب.
🔧 استخدامات super
في JavaScript:
الاستخدام | الهدف |
---|---|
super(args) | لاستدعاء constructor الخاص بالكلاس الأب |
super.methodName() | لاستدعاء دالة من الأب داخل كلاس الابن (حتى لو تم عمل override لها) |
📘 1. super()
داخل constructor:
تُستخدم لاستدعاء constructor الكلاس الأب من داخل constructor الكلاس الابن.
✅ مثال:
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 👈 ضروري: يستدعي constructor الأب
this.age = age; // 👈 خاص بالكلاس الابن
}
}
let c = new Child("Ali", 20);
console.log(c.name); // Ali
console.log(c.age); // 20
JavaScriptإذا لم تكتب
super()
قبل استخدامthis
في constructor الابن، ستحصل على خطأ.
📗 2. super.method()
لاستدعاء دوال الأب:
يمكنك استخدام super.method()
داخل أي دالة في كلاس الابن، حتى لو كنت عملت Override للدالة.
✅ مثال:
class Parent {
greet() {
return "Hello from Parent";
}
}
class Child extends Parent {
greet() {
let parentMessage = super.greet(); // 👈 استدعاء دالة الأب
return parentMessage + " and Hello from Child";
}
}
let obj = new Child();
console.log(obj.greet());
// Hello from Parent and Hello from Child
JavaScript📙 استخدام super في الدوال العادية مقابل constructor
السياق | الشكل | الوظيفة |
---|---|---|
داخل constructor | super(args) | استدعاء constructor الأب |
داخل دالة | super.method() | استدعاء دالة من كلاس الأب |
🛑 ملاحظات مهمة:
- يجب استدعاء
super()
قبل استخدامthis
داخل constructor. - لا يمكن استخدام
super
إلا داخل كلاس يرث من آخر (class Child extends Parent
). - لا يمكنك استخدام
super
في كلاس لا يرث (extends
) من كلاس آخر.
🔥 مثال شامل يوضح الحالتين:
class Vehicle {
constructor(type) {
this.type = type;
}
move() {
return `${this.type} is moving`;
}
}
class Car extends Vehicle {
constructor(type, brand) {
super(type); // ← يستدعي constructor الأب
this.brand = brand;
}
move() {
return super.move() + ` - Brand: ${this.brand}`;
}
}
let c = new Car("Car", "Toyota");
console.log(c.move());
// Car is moving - Brand: Toyota
JavaScript✅ خلاصة:
ما تعني super ؟ | متى تستخدم؟ |
---|---|
استدعاء constructor الأب | داخل constructor الابن مع super(args) |
استدعاء دالة من الأب | داخل دالة في الابن باستخدام super.method() |