25 август 2025

Prototip meros

Dasturlashda biz ko’pincha biron bir narsani olishni va uni kengaytirishni xohlaymiz.

Masalan, bizda user obyekti, uning xususiyatlari va usullari bor, va admin va mehmon ni uning biroz o’zgartirilgan variantlari sifatida qilishni xohlaymiz. Biz user da mavjud bo’lgan narsalarni qayta ishlatishni istaymiz, uning usullarini nusxa ko’chirmasligimiz/amalga oshirmasligimiz kerak, shunchaki uning ustiga yangi obyekt quramiz.

Prototipal meros – bunga yordam beradigan til xususiyati.

[[Prototype]]

JavaScript-da obyektlar maxfiy xususiyatga ega [[Prototype]] (spetsifikatsiyada ko’rsatilganidek), ya’ni null yoki boshqa obyektga murojaat qiladi. Ushbu obyekt “prototip” deb nomlanadi:

Bu [[Prototype]] “sehrli” ma’noga ega. Obyektdan xususiyatni o’qishni xohlaganimizda va u yetishmayotgan bo’lsa, JavaScript uni avtomatik ravishda prototipdan oladi. Dasturlashda bunday narsa “prototipli meros” deb nomlanadi. Ko’pgina ajoyib til xususiyatlari va dasturlash texnikasi unga asoslangan.

[[Prototype]] xususiyati ichki va yashirin, ammo uni o’rnatishning ko’plab usullari mavjud.

Ulardan biri quyidagicha ``proto` dan foydalanish:

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal
**proto** [[Prototype]]uchun tarixiy qabul qiluvchi/belgilovchi

Iltimos,**proto** [[Prototype]] bilan bir xil emas. Bu u uchun getter/setter.

U tarixiy sabablarga ko’ra mavjud bo’lib, zamonaviy tilda uning o’rnini Object.getPrototypeOf/Object.setPrototypeOf funktsiyalari egallaydi, bu prototipni oladi/ o’rnatadi. Buning sabablarini va ushbu funktsiyalarni keyinroq o’rganib chiqamiz.

__proto__ spetsifikatsiyasi bo’yicha faqat brauzerlar tomonidan qo’llab-quvvatlanishi kerak, ammo aslida barcha muhit, shu jumladan server tomoni uni qo’llab-quvvatlaydi. Hozircha, __proto__ yozuvi biroz intuitiv ravishda aniq bo’lgani uchun, biz buni misollarda qo’llaymiz.

Agar biz rabbit dan xususiyat qidirsak va u yetishmayotgan bo’lsa, JavaScript uni avtomatik ravishda animal dan oladi.

Masalan:

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

// hozir quyonda ikkala xususiyatni topishimiz mumkin:
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true

Bu yerda (*) satri animal rabbit ning prototipi sifatida o’rnatadi.

Keyin, alert rabbit.eats (**) xususiyatini o’qishga harakat qiladi, u rabbit da mavjud emas, shuning uchun JavaScript [[Prototype]] ma’lumotnomasiga amal qiladi va uni animal da topadi (pastdan yuqoriga qarang):

Bu yerda “animal – bu rabbit ning prototipi” yoki “rabbit prototipik ravishda animal dan meros qilib olinadi”.

Shunday qilib, agar animal juda ko’p foydali xususiyatlarga va usullarga ega bo’lsa, ular avtomatik ravishda rabbit da mavjud bo’ladi. Bunday xususiyatlar “meros qilib olingan” deb nomlanadi.

Agar bizda animal da usul bo’lsa, uni rabbit da chaqirish mumkin:

let animal = {
  eats: true,
  walk() {
    alert("Hayvon sayr qilyapti");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// walk prototipidan olingan
rabbit.walk(); // Hayvon sayr qilyapti

Usul prototipdan avtomatik ravishda quyidagicha olinadi:

Prototip zanjiri uzunroq bo’lishi mumkin:

let animal = {
  eats: true,
  walk() {
    alert("Hayvon sayr qilyapti");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// walk prototip zanjiridan olingan
longEar.walk(); // Hayvon sayr qilyapti
alert(longEar.jumps); // true (rabbit dan)

Aslida faqat ikkita cheklov mavjud:

  1. Havolalar doiralarda yurib bo’lmaydi. Agar doira ichida __proto__ ni belgilashga harakat qilsak, JavaScript xatolikka yo’l qo’yadi.
  2. __proto__ qiymati obyekt yoki null bo’lishi mumkin, boshqa turlari (masalan, ibtidoiylar) hisobga olinmaydi.

Bundan tashqari, bu aniq bo’lishi mumkin, ammo baribir: bitta [[Prototype]] bo’lishi mumkin. Obyekt boshqa ikkitadan meros ololmaydi.

Yozishda prototipdan foydalanilmaydi

Prototip faqat o’qish xususiyatlari uchun ishlatiladi.

Yozish/o’chirish operatsiyalari to’g’ridan-to’g’ri obyekt bilan ishlaydi.

Quyidagi misolda biz walk usulini rabbit ga tayinlaymiz:

let animal = {
  eats: true,
  walk() {
    /* bu usul quyon tomonidan ishlatilmaydi */
  }
};

let rabbit = {
  __proto__: animal
};

rabbit.walk = function() {
  alert("Quyon! Sakra-sakra!");
};

rabbit.walk(); // Quyon! Sakra-sakra!

Bundan buyon, rabbit.walk() chaqiruvi usulni darhol obyektda topadi va prototipdan foydalanmasdan amalga oshiradi:

Bu faqat ma’lumotlarning xususiyatlari uchun, lekin kiruvchilar uchun emas. Agar xususiyat getter/setter bo’lsa, u funktsiya kabi ishlaydi: getters/setters prototipda topiladi.

Shu sababli admin.fullName quyidagi kodda to’g’ri ishlaydi:

let user = {
  name: "John",
  surname: "Smith",

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  },

  get fullName() {
    return `${this.name} ${this.surname}`;
  },
};

let admin = {
  __proto__: user,
  isAdmin: true,
};

alert(admin.fullName); // John Smith (*)

// setter triggers!
admin.fullName = "Alice Cooper"; // (**)

alert(admin.fullName); // Alice Cooper, state of admin modified
alert(user.fullName); // John Smith, state of user protected

(*) Satrida admin.fullName xususiyati user prototipida qabul qiluvchiga ega, shuning uchun u shunday nomlanadi. Va (**) satrida prototipda xususiyat o’rnatuvchiga ega, shuning uchun u shunday nomlanadi.

“This” qiymati

Yuqoridagi misolda qiziq savol tug’ilishi mumkin: fullName(value) ichida this qiymati qanday? this.name va this.same xususiyatlari qayerda yozilgan: user yoki admin ga?

Javob oddiy: this ga prototiplar umuman ta’sir qilmaydi.

Usul qayerda bo’lishidan qat’i nazar: obyektda yoki uning prototipida. Usul chaqiruvida this har doim nuqta oldidagi obyekt hisoblanadi.

Shunday qilib, admin.fullName= setter chaqiruvi user emas, balki admin dan foydalanadi.

Bu aslida o’ta muhim narsa, chunki bizda ko’plab usullarga ega bo’lgan va undan meros bo’lib qolgan katta obyekt bo’lishi mumkin. Keyin meros qilib olingan obyektlar uning usullarini ishga tushirishi mumkin va ular bu obyektlarning holatini o’zgartiradi, kattani emas.

Masalan, bu yerda animal “usulni saqlash” ni anglatadi va quyon undan foydalanadi.

rabbit.sleep() chaqiruvi rabbit obyektida this.isSleeping ni o’rnatadi:

// animal has methods
let animal = {
  walk() {
    if (!this.isSleeping) {
      alert(`Men sayr qilyapman`);
    }
  },
  sleep() {
    this.isSleeping = true;
  },
};

let rabbit = {
  name: "White Rabbit",
  __proto__: animal,
};

// modifies rabbit.isSleeping
rabbit.sleep();

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)

Natija rasmi:

Agar bizda animal dan meros bo’lib qolgan qush, ilon va boshqalar kabi narsalar bo’lsa, ular animal usullaridan ham foydalanishlari mumkin edi. Ammo this har bir usulda animal emas, balki chaqiruv vaqtida (nuqta oldidan) baholanadigan mos keladigan obyektni bo’ladi. Shunday qilib, biz ma’lumotlarni this ga yozganimizda, ular ushbu obyektlarda saqlanadi.

Natijada, usullar birgalikda ishlatiladi, ammo obyekt holatida emas.

Xulosa

  • JavaScript-da barcha obyektlar yashiringan [[Prototype]] xususiyatiga ega, bu boshqa obyekt yoki null.
  • Bunga kirish uchun obj .__ proto__ dan foydalanishimiz mumkin (tarixiy getter/setter, boshqa usullar mavjud, yaqin orada ko’rib chiqilishi kerak).
  • [[Prototype]] ga havola qilingan obyekt “prototip” deb nomlanadi.
  • Agar biz obj xususiyatini o’qishni yoki usulni chaqirishni xohlasak va u mavjud bo’lmasa, JavaScript uni prototipda topishga harakat qiladi. Yozish/o’chirish operatsiyalari to’g’ridan-to’g’ri obyektda ishlaydi, ular prototipdan foydalanmaydi (agar xususiyat aslida setter bo’lmasa).
  • Agar biz obj.method() deb nomlasak va usul prototipdan olingan bo’lsa, this obj ga hali ham murojaat qiladi. Shunday qilib, usullar, agar ular meros qilib olingan bo’lsa ham, har doim ham mavjud obyekt bilan ishlaydi.

Vazifalar

Obyekt juftligini yaratadigan, keyin ularni o’zgartiradigan kod.

Jarayonda qaysi qiymatlar ko’rsatilgan?

let animal = {
  jumps: null,
};
let rabbit = {
  __proto__: animal,
  jumps: true,
};

alert(rabbit.jumps); // ? (1)

delete rabbit.jumps;

alert(rabbit.jumps); // ? (2)

delete animal.jumps;

alert(rabbit.jumps); // ? (3)

3 javob bo’lishi kerak.

  1. true, rabbit dan olingan.
  2. null, animal dan olingan.
  3. undefined, boshqa bunaqa xususiyat yo’q.

Vazifa ikki qismdan iborat.

Bizning obyektimiz bor:

let head = {
  glasses: 1,
};

let table = {
  pen: 3,
};

let bed = {
  sheet: 1,
  pillow: 2,
};

let pockets = {
  money: 2000,
};
  1. __proto__ dan foydalanib, prototiplarni har qanday xususiyatni qidirish yo’liga mos keladigan tarzda belgilang: pocketsbedtablehead. Masalan, pockets.pen 3 (table da topildi) va bed.glasses 1 (head da topildi) bo’lishi kerak.
  2. Savolga javob bering: glasses ni pockets.glasses yoki head.glasses sifatida olish tezroq? Agar kerak bo’lsa, benchmark.
  1. __proto__ qo’shaylik:

    let head = {
      glasses: 1,
    };
    
    let table = {
      pen: 3,
      __proto__: head,
    };
    
    let bed = {
      sheet: 1,
      pillow: 2,
      __proto__: table,
    };
    
    let pockets = {
      money: 2000,
      __proto__: bed,
    };
    
    alert(pockets.pen); // 3
    alert(bed.glasses); // 1
    alert(table.money); // undefined
  2. Zamonaviy interpretatorlarda, ishlash jihatidan, biz obyektdan yoki uning prototipidan xususiyatni olishi bilan farq qilmaydi. Ular xususiyat qayerdan topilganligini eslashadi va uni keyingi so’rovda qayta ishlatadilar.

    Masalan, pockets.glasses uchun ular glasses (boshida) qayerdan topganlarini eslashadi va keyingi safar u yerda qidirishadi. Ular, shuningdek, biror narsa o’zgargan taqdirda ichki keshlarni yangilash uchun yetarlicha aqlli, shuning uchun optimallashtirish xavfsizdir.

Bizda animal dan meros qolgan rabbit bor.

If we call rabbit.eat(), which object receives the full property: animal or rabbit? Agar biz rabbit.eat() deb nomlasak, qaysi obyekt to'liq xususiyatini oladi: animal yoki rabbit?

let animal = {
  eat() {
    this.full = true;
  },
};

let rabbit = {
  __proto__: animal,
};

rabbit.eat();

Javob: rabbit.

Buning sababi shundaki, this nuqta oldidagi obyekt, shuning uchun rabbit.eat() rabbit ni o’zgartiradi.

Xususiyatni qidirish va bajarish ikki xil narsadir. rabbit.eat usuli dastlab prototipda uchraydi, so’ngra this = rabbit bilan bajariladi

Bizda ikkita hamster bor: speedy va lazy umumiy hamster obyektidan merosxo’r.

Ulardan birini boqsak, ikkinchisi ham to’yadi. Nima uchun? Buni qanday tuzatish kerak?

let hamster = {
  stomach: [],

  eat(food) {
    this.stomach.push(food);
  },
};

let speedy = {
  __proto__: hamster,
};

let lazy = {
  __proto__: hamster,
};

// Bu ovqat topdi
speedy.eat("olma");
alert(speedy.stomach); // olma

// Unda ham bor, nega? iltimos tuzating
alert(lazy.stomach); // olma

Keling, speedy.eat("olma") chaqiurvida nimalar bo’layotganini diqqat bilan ko’rib chiqamiz.

  1. speedy.eat usuli prototipda topilgan (=hamster), so’ngra this=speedy (nuqta oldidagi obyekt) bilan bajariladi.

  2. Keyin this.stomach.push() stomach xususiyatini topishi va unga push ni chaqirishi kerak. this da (=speedy) stomach ni qidiradi, ammo hech narsa topilmadi.

  3. Keyin u prototip zanjiriga ergashib, hamster da stomach ni topadi.

  4. Keyin u prototipning oshqozoniga ovqatni qo’shib, ustiga push ni chaqiradi.

Shunday qilib, barcha hamsterlar bitta oshqozonni bo’lishadi!

Har safar stomach prototipdan olinadigan bo’lsa, u holda stomach.push uni “joyida” o’zgartiradi.

Iltimos e’tibor bering, this.stomach = oddiy tayinlashda bunday narsa bo’lmaydi.

let hamster = {
  stomach: [],

  eat(food) {
    // this.stomach.push o'rniga this.stomach o'rnating
    this.stomach = [food];
  }
};

let speedy = {
   __proto__: hamster
};

let lazy = {
  __proto__: hamster
};

// Speedy bittasi ovqat topdi
speedy.eat("olma");
alert( speedy.stomach ); // olma

// Lazy birining qorni bo'sh
alert( lazy.stomach ); // <nothing>

Endi barchasi yaxshi ishlaydi, chunki this.stomach= stomach izlamaydi. Qiymat to’g’ridan-to’g’ri this obyektiga yoziladi.

Shuningdek, har bir hamsterning o’z oshqozoniga ega ekanligiga ishonch hosil qilish orqali biz muammodan butunlay qochishimiz mumkin:

let hamster = {
  stomach: [],

  eat(food) {
    this.stomach.push(food);
  }
};

let speedy = {
  __proto__: hamster,
  stomach: []
};

let lazy = {
  __proto__: hamster,
  stomach: []
};

// Speedy bittasi ovqat topdi
speedy.eat("olma");
alert( speedy.stomach ); // olma

// Lazy birining qorni bo'sh
alert( lazy.stomach ); // <nothing>

Umumiy yechim sifatida, ma’lum bir obyektning holatini tavsiflovchi barcha xususiyatlar, masalan, yuqoridagi stomach odatda ushbu obyektga yoziladi. Bu bunday muammolarning oldini oladi.

O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <code> yorlig'ini ishlating, bir nechta satrlar uchun - ularni <pre> yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)