IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur42/65-dars21 daqiqa
Mundarija (32)

JavaScript class ichidan: prototip ustidagi "shakar" va uning muhim farqlari

Qisqacha: class — konstruktor funksiya va prototype ning qulay yozuvi: ichkarida o'sha funksiya, o'sha prototip zanjiri. Lekin u faqat "shakar" emas, bir necha himoyani ham qo'shadi. Klassni new siz chaqirib bo'lmaydi, ichi doim strict, metodlari for...in da ko'rinmaydi. super esa metod yozilgan klassga bog'langan. #private maydonlar umuman yangi imkoniyat — ularni eski sintaksisda yozib bo'lmaydi.

Bu darsda

  • class ni konstruktor funksiya va prototipga "qaytarib" yoza olasiz va ikkalasining tuzilmasi bir xilligini tekshira olasiz.
  • Klass va konstruktor funksiya orasidagi besh muhim farqni ayta olasiz.
  • Maydon va konstruktordagi tayinlash nega har xil ishlashini tushuntira olasiz.
  • super qanday qilib "o'z otasini" topishini — [[HomeObject]] ni — bilasiz.
  • vazifalar dagi VazifalarRoyxati ni JSON'ga to'g'ri aylanadigan qilasiz.

Oldin bilishingiz kerak: Konstruktor funksiya va new, Strict mode, Xususiyat deskriptorlari, OOP tamoyillari va kompozitsiya.

1. Nega bu kerak?

Internetda, kitoblarda va intervyularda bitta gapni ko'p eshitasiz: "JavaScript'dagi class — shunchaki sintaktik shakar". Bu gap yarim rost. Yarmi rostligini bilmasangiz, ikki xil xatoga tushasiz.

Birinchisi: "klass — boshqa tillardagidek alohida narsa" deb o'ylash. Unda typeof Taom nega "function" ekani, metod nega nusxada emas, prototipda turgani sir bo'lib qoladi. DevTools'da [[Prototype]] ni ko'rib adashasiz.

Ikkinchisi: "klass — konstruktor funksiyaning o'zi" deb o'ylash. Unda eski kodni klassga ko'chirganingizda kutilmagan TypeError lar chiqadi — chunki klass bir necha joyda qattiqroq.

Sintaktik shakar (syntactic sugar) — tilga yangi imkoniyat qo'shmaydigan, faqat mavjud narsani qulayroq yozishga imkon beradigan sintaksis. Masalan, x += 1 — x = x + 1 ning shakari. Hayotdan o'xshatish: telefondagi "Uyga" yorlig'i. Bosganingizda xarita ochiladi va uy manzili tanlanadi — siz qo'lda qiladigan o'sha ishlar. Yangi imkoniyat yo'q, faqat qulay yo'l.

Bugun class ni "yorliq" sifatida ochamiz: ichida nima bor, va qaysi joylarda u oddiy yorliqdan ko'proq. Bu dars yangi sintaksis o'rgatmaydi — oldingi darslardagi bilimlarni bir-biriga ulaydi. Prototip, new, strict mode va deskriptorlar shu yerda bitta rasmga yig'iladi.

2. Ichkarida — o'sha prototip

2.1 Bitta klass, ikki yozuv

Konstruktor funksiya va new darsida klasslarsiz shunday yozgan edik:

js
function TaomF(nom, narx) {
  this.nom = nom;
  this.narx = narx;
}
TaomF.prototype.tavsif = function () {
  return `${this.nom}: ${this.narx} so'm`;
};
TaomF.VALYUTA = "so'm";

class Taom {
  static VALYUTA = "so'm";
  constructor(nom, narx) {
    this.nom = nom;
    this.narx = narx;
  }
  tavsif() {
    return `${this.nom}: ${this.narx} so'm`;
  }
}

const a = new TaomF("Osh", 35000);
const b = new Taom("Osh", 35000);
console.log(typeof TaomF, typeof Taom); // function function
console.log(a.tavsif() === b.tavsif()); // true
console.log(Object.getPrototypeOf(b) === Taom.prototype); // true
console.log(Taom.prototype.constructor === Taom); // true

To'rt tekshiruv ham bir xil tuzilmani tasdiqladi:

  1. Klass — funksiya. class Taom aslida Taom nomli funksiya yaratadi; constructor ichidagi kod — shu funksiyaning tanasi.
  2. Metodlar — Taom.prototype da, xuddi TaomF.prototype.tavsif = ... dagidek.
  3. Nusxaning [[Prototype]] i — Taom.prototype (Prototip zanjiri).
  4. prototype.constructor — klassning o'ziga qaytuvchi havola.

static a'zo esa funksiyaning o'z xususiyati: Taom.VALYUTA — TaomF.VALYUTA = ... ning aynan o'zi (Getter/setter va static).

flowchart LR
  C["class Taom { ... }"]
  F["funksiya Taom<br/>constructor tanasi"]
  P["Taom.prototype<br/>tavsif(), get/set"]
  S["Taom.VALYUTA<br/>static a'zolar"]
  C --> F
  C --> P
  C --> S

Diagrammaga qarang: bitta class e'loni uch joyga taqsimlanadi. Konstruktor kodi — funksiyaga, metodlar va getterlar — prototipga, static lar — funksiyaning o'ziga. Konstruktor funksiya bilan bu uch qismni uchta alohida qatorda qo'lda yozardik.

2.2 Getter ham — eski vosita bilan

Klassdagi get katta() ham yangi mexanizm emas. Xususiyat deskriptorlari darsidagi Object.defineProperty bilan xuddi shunday getter'ni prototipga qo'lda qo'yish mumkin:

js
function TaomF(nom) {
  this.nom = nom;
}
Object.defineProperty(TaomF.prototype, "katta", {
  get() {
    return this.nom.toUpperCase();
  },
});

class Taom {
  constructor(nom) {
    this.nom = nom;
  }
  get katta() {
    return this.nom.toUpperCase();
  }
}

console.log(new TaomF("osh").katta, new Taom("osh").katta); // OSH OSH

Natija bir xil, joyi ham bir xil — prototip. Farq faqat yozuvda: klassda getter metod kabi bitta qator bilan e'lon qilinadi, eski yozuvda esa deskriptor obyekti kerak. Klass getter'i ham enumerable: false — defineProperty ning sukut qiymati bilan bir xil.

2.3 extends ham — o'sha zanjir

Meros darsidagi ikki zanjirni eslang. extends siz ularni qo'lda shunday ulardik:

js
function TaomF(nom) {
  this.nom = nom;
}
TaomF.prototype.tavsif = function () {
  return this.nom;
};

function IchimlikF(nom, hajm) {
  TaomF.call(this, nom); // super(nom) o'rniga
  this.hajm = hajm;
}
Object.setPrototypeOf(IchimlikF.prototype, TaomF.prototype);
Object.setPrototypeOf(IchimlikF, TaomF);

IchimlikF.prototype.tavsif = function () {
  const ota = TaomF.prototype.tavsif.call(this); // super.tavsif()
  return `${ota}, ${this.hajm} l`;
};

const choy = new IchimlikF("Ko'k choy", 0.5);
console.log(choy.tavsif()); // Ko'k choy, 0.5 l
console.log(choy instanceof TaomF); // true

Har qatorning klassdagi juftini toping:

  • TaomF.call(this, nom) — super(nom). call ota funksiyani shu this bilan chaqirdi.
  • Birinchi setPrototypeOf — nusxalar zanjiri, ikkinchisi — static lar zanjiri. extends ikkalasini bitta so'z bilan qiladi.
  • TaomF.prototype.tavsif.call(this) — super.tavsif(). Ota nomini qo'lda yozish kerak bo'ldi.

Klassdagi yozuv besh-olti qatorni bitta so'zga qisqartiradi. ES2015 dan oldin (2015-yilgacha) hamma JavaScript kodi shunday yozilgan. Eski kutubxonalarda va internetdagi eski javoblarda bu shaklni hali ko'p uchratasiz.

Tekshirib ko'ring: Yuqoridagi kodda ikkinchi setPrototypeOf qatori o'chirilsa, nima ishlamay qoladi?

Javob

Nusxalar hammasi ishlayveradi: choy.tavsif() va instanceof — birinchi zanjirda. Faqat static meros yo'qoladi: TaomF.dan kabi metod bo'lsa, IchimlikF.dan — undefined. Klasslarda bunday "yarim meros" xatosi bo'lmaydi — extends ikkala zanjirni har doim birga ulaydi.

2.4 DevTools'da ko'ring

Brauzerda ham tuzilma bir xil ko'rinadi. «Bahor» sahifasida DevTools konsolini oching (DevTools bilan debug qilish) va klassni yozing:

js
class Taom {
  #narx = 35000;
  constructor(nom) {
    this.nom = nom;
  }
  tavsif() {
    return this.nom;
  }
}
new Taom("Osh");

Natija yonidagi uchburchakni bosib oching. nom: "Osh" va #narx: 35000 — nusxaning o'zida. Pastda [[Prototype]]: Object — uni ham oching: constructor: class Taom va tavsif shu yerda. Yana bir [[Prototype]] — Object.prototype. Bu — «Bitta klass, ikki yozuv» bo'limidagi zanjirning o'zi, brauzer chizgan holda.

Bitta farqqa e'tibor bering: Chrome #narx ni ko'rsatadi, Node esa console.log da ko'rsatmaydi (Taom { nom: 'Osh' }). Bu DevTools'ning debug uchun qulayligi, xolos. Kodingiz baribir #narx ga tashqaridan kira olmaydi.

Amalda shu oynada ko'p savolga javob topasiz: "metod nusxadami yoki prototipdami?", "bu nusxa qaysi klassdan?", "getter qaytargan qiymat nima?" (getter qatoridagi (...) ni bosing — Chrome uni o'shanda chaqiradi).

3. Shakardan ko'proq: besh farq

"Faqat shakar" bo'lganida, ikki yozuv hamma joyda bir xil ishlardi. Ishlamaydi. Quyidagi besh farq — klass eski yozuvga qo'shgan himoyalar.

3.1 new siz chaqirib bo'lmaydi

Konstruktor funksiyani new siz chaqirish — jim xato. Bu blok oddiy skript sifatida (strict'siz) ishga tushadi:

js
function TaomF(nom) {
  this.nom = nom;
}

const osh = TaomF("Osh"); // new unutildi
console.log(osh); // undefined
console.log(globalThis.nom); // Osh

new siz this — global obyekt (Global obyekt va globalThis). Funksiya taomni yaratish o'rniga global nom o'zgaruvchisini yozib yubordi, osh esa undefined. Xato xabari yo'q — xato ancha keyin, boshqa joyda chiqadi.

Klassda esa:

js
class Taom {
  constructor(nom) {
    this.nom = nom;
  }
}

const osh = Taom("Osh");
text
TypeError: Class constructor Taom cannot be invoked without 'new'

Tarjimasi: "Taom klass konstruktorini new siz chaqirib bo'lmaydi". Xato darhol, aynan unutilgan joyda. Klass funksiya bo'lsa ham, uning ichida maxsus belgi bor: "meni faqat new bilan chaqiring".

3.2 Ichi doim strict

Strict mode darsida "use strict" jim xatolarni haqiqiy xatoga aylantirishini ko'rdik. Klass tanasi — "use strict" yozilmagan oddiy skriptda ham — avtomatik strict:

js
function narxniYoz() {
  yangiNarx = 38000; // let unutildi
  return "funksiya: jim o'tdi";
}

class Taom {
  narxniYoz() {
    yangiNarx2 = 38000; // let unutildi
  }
}

console.log(narxniYoz());
console.log(typeof yangiNarx); // number
new Taom().narxniYoz();

Konsolda:

text
funksiya: jim o'tdi
number
text
ReferenceError: yangiNarx2 is not defined

Bu blok ikki narsa chiqardi va keyin xato bilan to'xtadi (saytda "Ishga tushir" bosilsa, xato ham ko'rinadi). Oddiy funksiya let siz o'zgaruvchini jimgina global qilib yaratdi. Klass metodi esa xuddi shu xatoga darhol ReferenceError berdi.

Ikkinchi oqibat — this. Metodni nusxadan ajratib olsangiz, strict'da this — undefined (this yo'qolishi):

js
class Taom {
  constructor(nom) {
    this.nom = nom;
  }
  tavsif() {
    return this.nom;
  }
}

const tavsif = new Taom("Osh").tavsif;
tavsif();
text
TypeError: Cannot read properties of undefined (reading 'nom')

Konstruktor funksiyada (strict'siz) xuddi shu kod this sifatida global obyektni olardi va jimgina undefined qaytarardi. Klass xatoni ko'rinadigan qiladi.

3.3 Metodlar sanalmaydi

js
function TaomF(nom) {
  this.nom = nom;
}
TaomF.prototype.tavsif = function () {};

class Taom {
  constructor(nom) {
    this.nom = nom;
  }
  tavsif() {}
}

for (const kalit in new TaomF("Osh")) {
  console.log("funksiya:", kalit);
}
for (const kalit in new Taom("Osh")) {
  console.log("klass:", kalit);
}

Konsolda:

text
funksiya: nom
funksiya: tavsif
klass: nom

for...in prototip zanjiridagi sanaladigan xususiyatlarni ham aylanadi. TaomF.prototype.tavsif = ... — oddiy tayinlash, u sanaladigan xususiyat yaratadi. Shuning uchun for...in metodni ham "kalit" deb chiqardi. Klass metodlari esa enumerable: false bilan yoziladi (Xususiyat deskriptorlari) — for...in faqat ma'lumotni ko'rsatdi.

OOP tamoyillari darsidagi mixin farqi ham shundan: Object.assign metodlarni oddiy tayinlash bilan qo'shadi — ular sanaladi.

3.4 Hoisting: TDZ

js
const choy = new Ichimlik();

class Ichimlik {}
text
ReferenceError: Cannot access 'Ichimlik' before initialization

function e'loni to'liq "ko'tariladi" — uni e'lon qatoridan oldin ham chaqirish mumkin. Klass esa let va const kabi TDZ da turadi (Temporal Dead Zone): nomi ma'lum, lekin e'lon qatorigacha unga tegib bo'lmaydi. Tuzatish: klassni ishlatishdan oldin e'lon qiling — odatda faylning boshida.

3.5 super — [[HomeObject]] ga bog'langan

Bu farq eng nozigi. Eski yozuvda ota metodini nomi bilan chaqirardik: TaomF.prototype.tavsif.call(this). Klassda esa super.tavsif() — ota nomi yo'q. super otani qayerdan biladi?

Javob: klass ichida yozilgan har metod yashirin [[HomeObject]] ("uy obyekti") havolasini oladi — u qaysi prototipda yozilgani. super.tavsif() ning qidiruvi [[HomeObject]] ning [[Prototype]] idan boshlanadi. this dan emas!

Buni metodni boshqa klassga "qarzga" berib ko'rish mumkin:

js
class Taom {
  salom() {
    return "taom";
  }
}
class Ichimlik extends Taom {
  salom() {
    return `ichimlik → ${super.salom()}`;
  }
}

class Xodim {
  salom() {
    return "xodim";
  }
}
class Ofitsiant extends Xodim {}
Ofitsiant.prototype.salom = Ichimlik.prototype.salom;

console.log(new Ofitsiant().salom()); // ichimlik → taom

Kutilgan javob "ichimlik → xodim" edi: metod endi Ofitsiant da, uning otasi — Xodim. Lekin chiqdi "taom". Metodning [[HomeObject]] i — Ichimlik.prototype, u yerdan bir qavat yuqorida esa Taom.prototype. super metod qayerga ko'chirilganiga qaramaydi — u tug'ilgan uyini eslaydi.

Qadamma-qadam ko'ring — Keyingi tugmasini bosib, qidiruv qayerdan boshlanishiga qarang:

Amalda bu kamdan-kam muammo bo'ladi — metodlarni klasslar orasida ko'chirish odatiy ish emas. Lekin mixinlarda super ishlatsangiz yoki eski kodni klassga o'tkazsangiz, aynan shu sababli "noto'g'ri ota" chaqirilishi mumkin. Qoida: super — yozilgan joyning otasi.

3.6 Yana ikki kichik himoya

Klass metodi — konstruktor emas. Eski yozuvda TaomF.prototype.tavsif oddiy function edi, uni new bilan ham chaqirsa bo'lardi — ma'nosiz bo'lsa ham. Klass metodida esa:

js
class Taom {
  tavsif() {
    return "osh";
  }
}

const osh = new Taom();
new osh.tavsif();
text
TypeError: osh.tavsif is not a constructor

Tarjimasi: "osh.tavsif — konstruktor emas". Metod faqat chaqirish uchun, yangi obyekt yaratish uchun emas. Arrow funksiyalar ham xuddi shunday — ularda ham prototype yo'q.

Ikkinchisi: klass ichida klassning o'z nomi o'zgarmas — xuddi const kabi:

js
class Taom {
  buz() {
    Taom = null;
  }
}

new Taom().buz();
text
TypeError: Assignment to constant variable.

Tarjimasi: "o'zgarmas o'zgaruvchiga qiymat berib bo'lmaydi". Bu himoya static dan kabi metodlar uchun: ular ichidagi new Taom(...) doim aynan shu klassni ko'rsatadi. Klass tashqarisida esa Taom nomi let kabi — uni boshqa qiymatga almashtirish mumkin, lekin klass ichidagi kod buni sezmaydi.

4. Maydonlar va #private: haqiqiy yangilik

4.1 Maydon setter'ni chaqirmaydi

Class maydonlari — ES2022 qo'shimchasi. Ular konstruktordagi this.x = ... ga o'xshaydi, lekin bitta farq bilan:

js
class Taom {
  set narx(qiymat) {
    console.log(`setter: ${qiymat}`);
  }
}

class Ichimlik extends Taom {
  narx = 5000; // maydon
}

class Kompot extends Taom {
  constructor() {
    super();
    this.narx = 8000; // tayinlash
  }
}

const choy = new Ichimlik();
new Kompot();
console.log(Object.keys(choy)); // [ 'narx' ]

Konsolda:

text
setter: 8000
[ 'narx' ]

Konsolda setter: 5000 yo'q — Ichimlik yaratilganda setter umuman chaqirilmadi.

Kompot dagi this.narx = 8000 — oddiy tayinlash. U zanjir bo'ylab setter'ni topdi va chaqirdi. Ichimlik dagi narx = 5000 maydoni esa setter'ni chetlab o'tdi: u nusxaning o'ziga yangi xususiyat yaratdi. Maydon "yoz" emas, "e'lon qil" (Object.defineProperty kabi) ma'nosida ishlaydi.

Amaliy xulosa: ota klassda setter bo'lsa, bola klassda xuddi shu nomli maydon e'lon qilmang — tekshiruv ishlamay qoladi. Kerak bo'lsa, konstruktorda this.narx = ... yozing.

Tekshirib ko'ring: Getter/setter va static darsidagi Taom da set narx manfiy narxni rad etardi. class Arzon extends Taom { narx = -5; } yozilsa, new Arzon("Somsa").narx nima bo'ladi?

Javob

-5. Maydon setter'ni chaqirmay, nusxaning o'ziga narx xususiyatini yaratdi. Endi bu nusxada otaning getter va setter'i ham ishlamaydi — o'z narx i ularni soyalab qo'ydi. Tekshiruv jimgina chetlab o'tildi. Ota konstruktori this.narx = narx qilgan bo'lsa ham, bola maydoni undan keyin yoziladi va qiymatni almashtiradi.

4.2 #private — shakar emas

Klassdagi ko'p narsani eski sintaksisda yozsa bo'ladi. #narx ni esa — yo'q. _narx konventsiyasi faqat "iltimos, tegmang" degan kelishuv, closure esa har nusxa uchun alohida funksiyalar yaratadi (Private # maydon). #maydon esa:

  • oddiy xususiyat emas — Object.keys, for...in, JSON.stringify uni umuman ko'rmaydi;
  • klass tashqarisidan unga murojaat — sintaksis xatosi, kod hatto ishga tushmaydi;
  • prototip zanjirida meros olinmaydi — bola klass otaning #narx iga to'g'ridan-to'g'ri kira olmaydi, faqat ota metodlari orqali.

Shuning uchun aniqroq gap: "class — prototip ustidagi shakar, ustiga bir necha himoya va bitta yangi imkoniyat (#)".

4.3 Farqlar bitta jadvalda

Konstruktor funksiya class
new siz chaqiruv jim ishlaydi, global'ni buzadi TypeError
Ichki kod strict faqat "use strict" bilan doim strict
Prototip metodlari for...in da ko'rinadi ko'rinmaydi
E'londan oldin ishlatish mumkin (hoisting) TDZ — ReferenceError
Ota metodi Ota.prototype.m.call(this) super.m() ([[HomeObject]])
Private holat _ kelishuvi yoki closure #maydon

Tekshirib ko'ring: Hamkasbingiz "klass — faqat shakar, demak eski konstruktor funksiyani klassga ko'chirish xavfsiz" deydi. Qaysi uch holatda ko'chirilgan kod endi xato beradi?

Javob
  1. Kodning biror joyida new siz chaqiruv qolgan bo'lsa — TypeError. 2) Metodlar ichida let siz o'zgaruvchi yoki this yo'qolishi bo'lsa — strict tufayli ReferenceError/TypeError. 3) Klass e'lonidan oldin ishlatilsa — TDZ. Bonus: prototip metodlarini for...in bilan aylanadigan kod endi ularni ko'rmaydi. Bu xatolarning hammasi aslida eski kodda yashirin turgan xatolar — klass ularni ko'rinadigan qildi.

5. Ko'p uchraydigan xatolar

5.1 new ni unutish

TypeError: Class constructor Taom cannot be invoked without 'new' — «new siz chaqirib bo'lmaydi» bo'limida. Xabarda "Class constructor" bo'lsa — chaqiruv joyiga new qo'shing. Factory metod (Taom.dan(...)) ishlatayotgan bo'lsangiz, new uning ichida.

5.2 Klass nusxasini nusxalash yoki JSON'ga aylantirish

js
class Taom {
  #narx = 35000;
  constructor(nom) {
    this.nom = nom;
  }
  tavsif() {
    return this.nom;
  }
}

const osh = new Taom("Osh");
console.log(JSON.stringify(osh)); // {"nom":"Osh"}
const nusxa = structuredClone(osh);
console.log(nusxa.tavsif); // undefined

JSON.stringify faqat nusxaning o'z sanaladigan xususiyatlarini oldi: #narx va metodlar yo'qoldi. structuredClone ham shunday: nusxa — oddiy obyekt, klassdan ajralgan, metodlarsiz. Tuzatish — toJSON va factory metod; buni «Vazifalar qadami» da vazifalar ga qo'shamiz.

5.3 Klass ichidagi metodni callback qilib berish

«Ichi doim strict» bo'limidagi Cannot read properties of undefined (reading 'nom'). Klass metodlarini addEventListener yoki map ga berishda arrow bilan o'rang yoki bind qiling (this yo'qolishi).

5.4 Ota setter'i bilan bir xil nomli maydon

«Maydon setter'ni chaqirmaydi» bo'limidagi holat: xato xabari yo'q, tekshiruv esa ishlamaydi. Bunday xato odatda kech topiladi — noto'g'ri qiymat bazaga yoki ekranga chiqqanda. Tuzatish: bola klassda otaning accessor xususiyati nomi bilan maydon e'lon qilmang; boshlang'ich qiymatni super(...) ga argument qilib bering yoki konstruktorda tayinlang.

5.5 "Klass — boshqa tillardagidek" deb o'ylash

Java yoki C# dan kelgan dasturchilar ko'pincha metod nusxada saqlanadi, this doim nusxa, klass e'londan oldin ham bor deb o'ylaydi. JavaScript'da uchalasi ham boshqacha: metod — prototipda, this — chaqiruvga bog'liq, klass — TDZ'da. Kutilmagan xulq ko'rsangiz, avval "bu ichkarida qanday prototip kodi?" deb so'rang — javob odatda shu darsdagi jadvalda.

6. Mashqlar

1-mashq (oson): Klassni "ochib" yozing

Quyidagi klassni konstruktor funksiya va prototype bilan qayta yozing. static ni ham unutmang. So'ng new siz chaqiruv natijasini ikkalasida solishtiring (faqat fikrda — kodda new bilan qoldiring).

js
class Bron {
  static MAKS_ODAM = 20;
  constructor(ism, odam) {
    this.ism = ism;
    this.odam = odam;
  }
  matn() {
    return `${this.ism}, ${this.odam} kishi`;
  }
}
Yechim
js
function Bron(ism, odam) {
  this.ism = ism;
  this.odam = odam;
}
Bron.MAKS_ODAM = 20;
Bron.prototype.matn = function () {
  return `${this.ism}, ${this.odam} kishi`;
};

const bron = new Bron("Malika", 4);
console.log(bron.matn(), Bron.MAKS_ODAM); // Malika, 4 kishi 20

new siz: klass — TypeError; funksiya — strict'siz skriptda global ism va odam ni yozib, undefined qaytaradi. Modul faylda (.mjs) esa this — undefined va Cannot set properties of undefined xatosi chiqadi.

2-mashq (o'rta): Nima chiqadi?

Kodni ishga tushirmasdan oldin har qatorning natijasini yozing, keyin tekshiring.

js
class Taom {
  turi = "taom";
  constructor(nom) {
    this.nom = nom;
  }
  tavsif() {
    return this.nom;
  }
  get katta() {
    return this.nom.toUpperCase();
  }
}

const osh = new Taom("Osh");
console.log(Object.keys(osh)); // [ 'turi', 'nom' ]
console.log(Object.getOwnPropertyNames(Taom.prototype));
// [ 'constructor', 'tavsif', 'katta' ]
console.log(JSON.stringify(osh)); // {"turi":"taom","nom":"Osh"}
console.log(osh.hasOwnProperty("tavsif")); // false
Yechim
  • Object.keys(osh) — [ 'turi', 'nom' ]: maydon (turi) konstruktor tanasidan oldin yoziladi, shuning uchun birinchi.
  • Prototipda — constructor, tavsif va getter katta. Ular sanalmaydi, lekin getOwnPropertyNames sanalmaydiganlarni ham ko'rsatadi.
  • JSON'da faqat o'z ma'lumot xususiyatlari: katta getteri prototipda — kirmadi.
  • tavsif — nusxaning o'zida emas, prototipda: false.

3-mashq (qiyin): super ni qo'lda

Ichimlik klassini klass sintaksisisiz yozing: TaomF dan meros, tavsif() ota natijasiga " (ichimlik)" qo'shsin, static dan(m) ham meros bo'lsin va chaqirgan klass nusxasini yaratsin. Ishora: static da new this(...); ota metodi — call bilan.

Yechim
js
function TaomF(nom) {
  this.nom = nom;
}
TaomF.prototype.tavsif = function () {
  return this.nom;
};
TaomF.dan = function (m) {
  return new this(m.nom);
};

function IchimlikF(nom) {
  TaomF.call(this, nom);
}
Object.setPrototypeOf(IchimlikF.prototype, TaomF.prototype);
Object.setPrototypeOf(IchimlikF, TaomF);
IchimlikF.prototype.tavsif = function () {
  return `${TaomF.prototype.tavsif.call(this)} (ichimlik)`;
};

const kompot = IchimlikF.dan({ nom: "Kompot" });
console.log(kompot.tavsif());
console.log(kompot instanceof IchimlikF);

Konsolda:

text
Kompot (ichimlik)
true

IchimlikF.dan ikkinchi setPrototypeOf tufayli topildi; ichida this — IchimlikF, shuning uchun nusxa ichimlik. TaomF.prototype.tavsif.call(this) — super.tavsif() ning qo'lda yozilgan shakli. Bu kod ishlaydi, lekin klassdagidan ikki baravar uzun va har ota nomi qo'lda — extends va super nega qo'shilgani shu yerda ko'rinadi.

4-mashq: Vazifalar qadami — royxatni JSON'ga qaytarish

refactor/vazifalar-royxati branch'ida davom etamiz. O'tgan darsdagi sinov ro'yxatiga qayta qarang: unda bitta tugma yo'q edi — eksport (JSON asoslari). U holatni JSON.stringify bilan matnga aylantiradi. Private # maydon darsida Vazifa ga aynan shu uchun toJSON qo'shgan edik. 11-qismda saqlash ham shu yo'l bilan ishlaydi.

Hozir tekshirib ko'ring. «JSON qilib chiqarish» tugmasini bosing, keyin DevTools konsolida yozing:

js
JSON.stringify(holat.vazifalar.top(1));
// '{"id":1,"matn":"Non olish","bajarildi":false}'
JSON.stringify(holat.vazifalar);
// '{}'

Bitta vazifa to'g'ri — uning toJSON i ishlayapti. Butun royxat esa bo'sh obyekt, eksport maydonida ham "vazifalar": {}! O'tgan darsgacha holat.vazifalar massiv edi va JSON.stringify har elementning toJSON ini chaqirardi. Endi u VazifalarRoyxati nusxasi. Unda faqat #vazifalar va #yangiId bor — ikkalasi private, JSON.stringify ularni ko'rmaydi.

Bu — «Klass nusxasini JSON'ga aylantirish» xatosi. Refaktoring ro'yxatdagi xulqni o'zgartirmadi, lekin eksportni jimgina buzdi: konsolda xato yo'q, ro'yxat to'g'ri chiziladi. Sinov ro'yxatida bo'lmagan imkoniyat — sinalmagan imkoniyat.

Vazifa: VazifalarRoyxati ga toJSON() metodini qo'shing (JSON chuqur darsidagi toJSON) — u vazifalarning oddiy obyektlar massivini qaytarsin. So'ng VazifalarRoyxati.dan(JSON.parse(matn)) royxatni to'liq tiklashini tekshiring. Ilova xulqi o'zgarmasin.

Yechim
js
toJSON() {
  return this.#vazifalar.map((v) => v.toJSON());
}

Royxat ichki massivni emas, yangi massivni qaytaradi — OOP tamoyillari darsidagi "Ichki massivni tashqariga berib yuborish" xatosidan himoya. toJSON ni har kim chaqira oladi, JSON.stringify esa faqat birinchisi.

Node'da to'liq aylanishni tekshiring — matnga va qaytib. Klasslar qisqartirilgan (faqat aylanishga kerakli qismi):

js
class Vazifa {
  id;
  bajarildi;
  #matn;
  constructor(id, matn, bajarildi = false) {
    this.id = id;
    this.#matn = matn;
    this.bajarildi = bajarildi;
  }
  static dan(m) {
    return new Vazifa(m.id, m.matn, m.bajarildi);
  }
  toJSON() {
    const { id, bajarildi } = this;
    return { id, matn: this.#matn, bajarildi };
  }
}

class VazifalarRoyxati {
  #vazifalar = [];
  static dan(malumotlar) {
    const yangi = new VazifalarRoyxati();
    yangi.#vazifalar = malumotlar.map((m) => Vazifa.dan(m));
    return yangi;
  }
  toJSON() {
    return this.#vazifalar.map((v) => v.toJSON());
  }
}

const royxat = VazifalarRoyxati.dan([
  { id: 1, matn: "Non olish", bajarildi: false },
  { id: 2, matn: "DOM darsini takrorlash", bajarildi: true },
]);
const matn = JSON.stringify(royxat);
console.log(matn);
const tiklangan = VazifalarRoyxati.dan(JSON.parse(matn));
console.log(JSON.stringify(tiklangan) === matn); // true

Konsolda:

text
[{"id":1,"matn":"Non olish","bajarildi":false},{"id":2,"matn":"DOM darsini takrorlash","bajarildi":true}]
true

Ikki tomonlama yo'l tayyor: toJSON — klassdan matnga, dan — matndan klassga. Sizning faylingizda #yangiId ham bor — u saqlanmaydi, dan generatorni eng katta id dan qayta yasaydi.

Brauzerni yangilab, qayta tekshiring. Konsolda JSON.stringify(holat.vazifalar) uch vazifani to'liq ko'rsatadi, eksport maydonida esa yana uch vazifa — refaktoringdan oldingi natija bilan harfma-harf bir xil. JSON.stringify royxatning toJSON ini o'zi chaqirdi, eksport kodiga tegmadik. Qolgan hamma narsa (qo'shish, belgilash, tahrir, filtr, fokus) — avvalgidek.

Commit: Ro'yxat: VazifalarRoyxati ga toJSON qo'sh.

7. Real ishda

  • Eski kod. 2015-yilgacha yozilgan kutubxonalar va ko'p brauzer polyfill'lari — konstruktor funksiya va prototype da. Ularni o'qish va klassga ko'chirish — real loyihalardagi tez-tez uchraydigan vazifa. Bu darsdagi farqlar ro'yxati — ko'chirish uchun nazorat varag'i.
  • Transpilyatorlar. Transpilyator — yangi JavaScript kodini eski brauzer tushunadigan kodga o'giradigan dastur. Babel va TypeScript (15–16-qismlarda o'rganamiz, hozir bilish shart emas) yangi kodni eski brauzerlar uchun qayta yozganda class ni aynan shu darsdagidek funksiya va prototype ga aylantiradi — new tekshiruvi uchun maxsus yordamchi bilan.
  • Tezlik. JavaScript dvigatellari (Chrome'dagi V8) bir xil tuzilmadagi obyektlarni tezroq ishlaydi. Klass nusxalari doim bir xil maydonlar bilan, bir xil tartibda yaratiladi — shuning uchun ular dvigatel uchun qulay. Bu mavzuni JS dvigateli ichida darsida chuqurroq ko'ramiz, hozir bilish shart emas.
  • Saqlash va tarmoq. Klass nusxasi JSON'ga o'z-o'zidan to'g'ri aylanmaydi. Ma'lumotni serverga yuborish yoki saqlash (11-qism) — toJSON + factory metod juftligi bilan.
  • Intervyu. "class faqat sintaktik shakarmi?" — o'rta darajadagi sevimli savol. Yaxshi javob: "Asosan ha — o'sha prototip; lekin new majburiy, ichi strict, metodlar sanalmaydi, TDZ, super [[HomeObject]] orqali, #private esa umuman yangi."

Xulosa

  • class — funksiya + prototype + static xususiyatlar; nusxa zanjiri konstruktor funksiyadagidek.
  • extends — ikki setPrototypeOf, super(...) — Ota.call(this, ...), super.m() — Ota.prototype.m.call(this).
  • Klass new siz chaqirilmaydi, ichi doim strict, metodlari sanalmaydi, e'londan oldin TDZ'da.
  • super metod yozilgan joyning otasini qidiradi ([[HomeObject]]), this ning emas.
  • Maydon e'lon qiladi (setter'ni chetlab o'tadi), konstruktordagi tayinlash esa setter'ni chaqiradi.
  • #private — shakar emas, yangi imkoniyat; JSON uni ko'rmaydi — kerak bo'lsa toJSON yozing.

Keyingi dars: JS'da dizayn naqshlari — kuzatuvchi, modul, factory, singleton va boshqa tayyor yechim qoliplarini «Bahor» misollarida ko'ramiz va vazifalar da render'ni kuzatuvchi orqali chaqiramiz.

Manbalar

  • MDN: "Classes", "Public class fields", "super", "Strict mode" — developer.mozilla.org
  • ECMAScript 2025 Language Specification: "ClassDefinitionEvaluation", "MakeMethod" ([[HomeObject]]), "DefineField" — tc39.es/ecma262
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript class ichidan: prototip ustidagi "shakar" va uning muhim farqlari — IlmHamroh