Inheritance
Prototype orqali meros — eski ES5 usuli va extends/super bilan zamonaviy usul, super() qanday this yaratadi, super.method() va ko'p darajali meros.
28-dars
Inheritance
1. Muammo — nega meros kerak?
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return `${this.name} ovqatlanmoqda`;
};
function Dog(name, breed) {
this.name = name;
this.breed = breed;
}
Dog.prototype.bark = function() {
return `${this.name} vov-vov!`;
};
Bu yerda Dog va Animal bir-biridan mustaqil — Dog instance'lari eat() metodidan foydalana olmaydi, garchi mantiqan har bir it — hayvon bo'lsa ham. Kodni takrorlamasdan, Dogga Animalning barcha imkoniyatlarini "meros qilib olish" kerak. Aynan shuning uchun Inheritance kerak.
2. Eski usul (ES5) — Prototype orqali qo'lda meros
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return `${this.name} ovqatlanmoqda`;
};
function Dog(name, breed) {
Animal.call(this, name); // 1-QADAM: ota constructor'ni chaqirish
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 2-QADAM: prototype zanjirini ulash
Dog.prototype.constructor = Dog; // 3-QADAM: constructor'ni tiklash
Dog.prototype.bark = function() {
return `${this.name} vov-vov!`;
};
Bu yerda 3 ta muhim qadam bor — har birini tushunish shart:
1-qadam — Animal.call(this, name): bu — call mexanizmi. Animal funksiyasini this = Dog instance bilan chaqiryapmiz, shunda this.name = name — Dog instance'ining o'ziga yoziladi. Bu — ota constructor'ning property yaratish logikasini ishlatish.
2-qadam — Dog.prototype = Object.create(Animal.prototype): bu — zanjirni ulash. Object.create(Animal.prototype) — yangi, bo'sh obyekt yaratadi, uning [[Prototype]]i Animal.prototypega bog'langan holda:
Dog.prototype (yangi, bo'sh)
[[Prototype]] ──→ Animal.prototype { eat }
Nega Dog.prototype = Animal.prototype (to'g'ridan-to'g'ri teng) emas? Chunki agar shunday qilsangiz, Dog.prototype va Animal.prototype bitta xil obyektga ishora qilardi — Dog.prototypega bark qo'shsangiz, u Animal.prototypega ham qo'shilib qolardi (chunki ular bir xil obyekt)! Object.create esa yangi, alohida obyekt yaratib, uni zanjirga ulaydi — bu ikkalasini mustaqil qiladi.
3-qadam — constructorni tiklash: prototypeni almashtirish constructor pointerini yo'qotadi. Shu sababli qo'lda tiklash kerak.
3. To'liq zanjir — eski usulda qanday ko'rinadi
const d1 = new Dog('Rex', 'Labrador');
d1 → Dog.prototype { bark, constructor→Dog } → Animal.prototype { eat } → Object.prototype → null
d1.bark(); // 'Rex vov-vov!' — Dog.prototype'da topildi
d1.eat(); // 'Rex ovqatlanmoqda' — Dog.prototype'da yo'q, zanjir bo'ylab Animal.prototype'da topildi!
d1 instanceof Dog; // true
d1 instanceof Animal; // true — chunki zanjirda Animal.prototype bor!
Bu — Prototype Chain mavzusida ko'rgan bir xil qidiruv mexanizmi, faqat endi zanjir bir bosqich uzunroq: d1 → Dog.prototype → Animal.prototype → Object.prototype → null.
4. Zamonaviy usul — class + extends + super
Xuddi shu narsa, lekin qulay sintaksis bilan:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
return `${this.name} ovqatlanmoqda`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Animal.call(this, name) ga TENG
this.breed = breed;
}
bark() {
return `${this.name} vov-vov!`;
}
}
const d1 = new Dog('Rex', 'Labrador');
d1.eat(); // 'Rex ovqatlanmoqda'
d1.bark(); // 'Rex vov-vov!'
extends — ichkarida avtomatik ravishda eski usulning 2 va 3-qadamini bajaradi:
class Dog extends Animal { ... }
// ICHKARIDA AVTOMATIK:
// Dog.prototype = Object.create(Animal.prototype);
// Dog.prototype.constructor = Dog;
super(name) esa — 1-qadamni bajaradi:
super(name);
// ICHKARIDA: Animal.call(this, name) ga JUDA YAQIN (aynan emas, pastda farqini ko'ramiz)
5. super() — nozik, muhim farq (constructor function'dan)
new mexanizmining 4 qadamini eslang:
1. newObj = {}
2. newObj.[[Prototype]] = Fn.prototype
3. Fn.call(newObj, ...args)
4. return tekshirish
Oddiy (meros olmagan) classda bu 1-qadam (this yaratish) avtomatik, constructor ishga tushishidan oldin bo'ladi.
Lekin extends ishlatilgan classda — bu boshqacha! this super() chaqirilmaguncha yaratilmaydi:
class Dog extends Animal {
constructor(name, breed) {
console.log(this); // ❌ ReferenceError: Must call super constructor before accessing 'this'
super(name);
console.log(this); // ✅ ENDI ishlaydi — this mavjud
this.breed = breed;
}
}
Nega shunday? Chunki thisni kim yaratishi kerakligi — bu ota class (Animal)ning vazifasi hisoblanadi (chunki Animal — "asosiy" constructor, u thisni to'liq shakllantiradi). Dog — faqat qo'shimcha qiladi. Shu sababli JS majburlaydi: super() chaqirilmaguncha, this umuman mavjud emas — uni ishlatishga urinish xato beradi.
Dog constructor ichida:
┌─────────────────────────────────────┐
│ this HALI YO'Q │
│ super(name); ← Animal constructor │
│ ichida this YARATILADI │
│ (Animal.prototype'ga bog'langan │
│ holda, lekin keyin Dog.prototype │
│ bilan almashtiriladi — pastda) │
├─────────────────────────────────────┤
│ this ENDI MAVJUD │
│ this.breed = breed; ✅ │
└─────────────────────────────────────┘
6. Metodni "override" qilish (qayta belgilash) va super.method()
class Animal {
eat() {
return `${this.name} ovqat yeyapti`;
}
}
class Dog extends Animal {
eat() {
const base = super.eat(); // OTA klassning eat()'ini chaqirish
return `${base}, ayniqsa suyakni yoqtiradi`;
}
}
const d1 = new Dog();
d1.name = 'Rex';
console.log(d1.eat()); // 'Rex ovqat yeyapti, ayniqsa suyakni yoqtiradi'
Bu yerda Dog.prototype.eat — Animal.prototype.eatni shadowing qilyapti. Lekin super.eat() orqali — shadowing qilingan bo'lsa ham, ota versiyaga maxsus yo'l orqali murojaat qilish mumkin.
V8 ichida super.eat() qanday ishlaydi? U oddiy this.eat() emas — u to'g'ridan-to'g'ri Animal.prototype.eatga ishora qiladi, thisning o'z prototype zanjiridan qidirmasdan:
super.eat() → joriy klassning [[HomeObject]] orqali,
to'g'ridan-to'g'ri Animal.prototype.eat topiladi,
lekin this hali ham Dog instance (d1) bo'lib qoladi
Bu — funksiya ichida yashiringan [[HomeObject]] degan ichki slot orqali ishlaydi (class metodlari buni avtomatik oladi) — u "men qaysi prototype'da yozilganman" degan ma'lumotni saqlaydi, shu orqali super — bir bosqich yuqoriga to'g'ri yo'l topadi.
7. To'liq zanjir — vizual, hammasi birga
class Animal {
constructor(name) { this.name = name; }
eat() { return `${this.name} yeyapti`; }
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() { return `${this.name} vov!`; }
}
const d1 = new Dog('Rex', 'Labrador');
d1 { name: 'Rex', breed: 'Labrador' }
│
│ [[Prototype]]
▼
Dog.prototype { bark, constructor→Dog }
│
│ [[Prototype]]
▼
Animal.prototype { eat, constructor→Animal }
│
│ [[Prototype]]
▼
Object.prototype { toString, hasOwnProperty, ... }
│
│ [[Prototype]]
▼
null
d1.bark(); // Dog.prototype'da topildi
d1.eat(); // Dog.prototype'da yo'q → Animal.prototype'da topildi
d1 instanceof Dog; // true
d1 instanceof Animal; // true
d1 instanceof Object; // true
8. instanceof zanjir bo'ylab qanday ishlaydi (meros bilan)
console.log(d1 instanceof Dog); // Dog.prototype d1'ning zanjirida bormi? HA
console.log(d1 instanceof Animal); // Animal.prototype d1'ning zanjirida bormi? HA (2 bosqich yuqorida)
console.log(d1 instanceof Object); // Object.prototype d1'ning zanjirida bormi? HA (3 bosqich yuqorida)
instanceof — zanjirning istalgan chuqurligida qidirishi mumkin, faqat u qanchalik chuqur bo'lsa, tekshiruv shunchalik ko'p qadam oladi (garchi bu odatda juda tez, chunki zanjirlar odatda qisqa).
9. Ko'p darajali meros (chain of inheritance)
class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}
const d1 = new Dog();
d1 → Dog.prototype → Mammal.prototype → Animal.prototype → Object.prototype → null
Har bir extends — zanjirga bitta qo'shimcha bo'g'in qo'shadi. Nazariy jihatdan zanjir cheksiz uzun bo'lishi mumkin, lekin amaliyotda juda chuqur meros zanjirlari — performance uchun yomon (har bir property/metod qidiruvi ko'proq bosqich talab qiladi) va tushunishni qiyinlashtiradi. Shu sababli zamonaviy dizaynda ko'pincha composition (tarkib) — meros o'rniga obyektlarni birlashtirish — afzal ko'riladi.
Xulosa
Inheritance — bu bir prototype'ni ikkinchisiga [[Prototype]] orqali ulash: eski usulda Object.create(Parent.prototype) bilan qo'lda, zamonaviy usulda extends bilan avtomatik. super() — ota constructor'ni chaqiradi va (meros olingan class'da) aynan shu qator thisni yaratadi — shu sababli super()dan oldin thisga tegib bo'lmaydi. super.method() — shadowing qilingan metodning ota versiyasiga [[HomeObject]] orqali to'g'ridan-to'g'ri, zanjirni "sakrab o'tib" murojaat qiladi.