الوراثة في لغة JavaScript
يا هلا بالشباب! اليوم بنتكلم عن موضوع مهم جداً في JavaScript وهو الوراثة (Inheritance). يمكن لو جاي من لغات زي Java أو C++ تكون متعود على كلاسات وأشياء زي كذا، لكن في JavaScript الموضوع مختلف شويتين، ومبني على حاجة اسمها 'النماذج الأولية' أو Prototypes.
الوراثة القائمة على النماذج الأولية (Prototype-based Inheritance)
في JavaScript، ما عندنا وراثة كلاسات بالمعنى التقليدي قبل ES6. الوراثة هنا تعتمد على الـ Prototypes. كل أوبجكت في JavaScript عنده 'نموذج أولي' (Prototype) بيورث منه الخصائص والدوال. تقدر تعتبره كأنه أب روحي للأوبجكت.
كيف تشوف الـ Prototype؟ فيه خاصية اسمها __proto__ (لاحظ الشرطتين اللي تحت) أو تقدر تستخدم Object.getPrototypeOf().
ملاحظة: الـ
__proto__هي طريقة قديمة وممكن ما تكون موجودة في كل البيئات، الأفضل استخدامObject.getPrototypeOf()أوObject.setPrototypeOf().
شوف هذا المثال البسيط:
const animal = {
makeSound() {
console.log('Some generic sound');
}
};
const dog = {
bark() {
console.log('Woof!');
}
};
// Dog inherits from animal
Object.setPrototypeOf(dog, animal);
dog.makeSound(); // Output: Some generic sound
dog.bark(); // Output: Woof!
console.log(Object.getPrototypeOf(dog) === animal); // Output: true
هنا، dog ورث دالة makeSound من animal. هذا هو جوهر الوراثة القائمة على الـ Prototypes.
الوراثة باستخدام دوال البناء (Constructor Functions)
قبل ظهور الكلاسات في ES6، كنا نستخدم دوال البناء (Constructor Functions) عشان نسوي 'كلاسات' ووراثة.
function Animal(name) {
this.name = name;
}
Animal.prototype.makeSound = function() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.name} makes a sound.</code>);
};
function Dog(name, breed) {
Animal.call(this, name); // Call the parent constructor
this.breed = breed;
}
// Set up the prototype chain
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Important: Reset constructor reference
Dog.prototype.bark = function() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.name} barks! It's a ${this.breed}.</code>);
};
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.makeSound(); // Output: Buddy makes a sound.
myDog.bark(); // Output: Buddy barks! It's a Golden Retriever.
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
تذكر:
Animal.call(this, name)تستخدم لاستدعاء دالة البناء الأب في سياق الكائن الحالي (this)، وهذا عشان نورث الخصائص اللي داخل الـ constructor.و
Dog.prototype = Object.create(Animal.prototype)هي الطريقة الصحيحة لربط الـ Prototype chain بدون ما ننسخ الـAnimal.prototypeنفسه، وبالتالي أي تغييرات فيAnimal.prototypeما تأثر علىDog.prototypeمباشرة.
Dog.prototype.constructor = Dog;مهم عشان الـconstructorالخاص بالكائن يشير إلىDogبدلاً منAnimal.
وراثة الكلاسات في ES6 (Syntactic Sugar)
مع ES6، جابوا لنا class و extends اللي تخلي الوراثة شكلها أقرب للغات الثانية، لكن في الحقيقة، هي مجرد 'سكر نحوي' (Syntactic Sugar) فوق الوراثة القائمة على الـ Prototypes اللي شفناها قبل شوي. يعني ما غيرت طريقة عمل JavaScript الأساسية، بس سهلت علينا الكتابة.
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.name} makes a sound.</code>);
}
static describe() {
console.log('This is a generic animal class.');
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call the parent class constructor
this.breed = breed;
}
bark() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.name} barks! It's a ${this.breed}.</code>);
}
// Override parent method
makeSound() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.name} howls and barks!</code>);
}
}
const myCat = new Animal('Whiskers');
myCat.makeSound(); // Output: Whiskers makes a sound.
const myNewDog = new Dog('Max', 'Labrador');
myNewDog.makeSound(); // Output: Max howls and barks! (Overridden method)
myNewDog.bark(); // Output: Max barks! It's a Labrador.
Animal.describe(); // Output: This is a generic animal class.
// myNewDog.describe(); // Error: describe is not a function (static method)
الـ
super(name)داخلconstructorالكلاس الابن ضرورية جداً. هي تستدعيconstructorالكلاس الأب عشان تهيئ الخصائص اللي ورثتها منه. لازم تكون أول سطر فيconstructorالكلاس الابن.وكمان، تقدر تسوي
overrideلدوال الأب (مثلmakeSoundهنا) عشان تعطيها سلوك خاص بالكلاس الابن.الـ
staticmethods تكون خاصة بالكلاس نفسه مو بالكائنات اللي تنشئها منه. يعني تستدعيها باسم الكلاس مباشرة.
خلاصة الكلام
الوراثة في JavaScript مبنية على الـ Prototypes في الأساس. الكلاسات في ES6 سهلت كتابة الكود وخليته أوضح، لكنها في النهاية ما غيرت المبدأ الأساسي. فهمك للـ Prototypes بيخليك مبرمج JavaScript أقوى وأفهم لطريقة عمل اللغة.
أتمنى يكون الدرس خفيف ومفيد لكم! نشوفكم في درس ثاني.