Series: JavaScript Basics Lesson 28

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.