25 август 2025

Prototip usullari, objects without **proto**

Ushbu bo’limning birinchi bobida biz prototipni o’rnatish uchun zamonaviy usullar mavjudligini eslatib o’tdik.

__proto__ biroz eskirgan hisoblanadi (JavaScript standartining faqat brauzer qismida).

Zamonaviy usullar:

Ular __proto__ o’rniga ishlatilishi kerak.

Masalan:

let animal = {
  eats: true
};

// prototip sifatida animal bilan yangi obyekt yaratish
let rabbit = Object.create(animal);

alert(rabbit.eats); // true

alert(Object.getPrototypeOf(rabbit) === animal); // rabbit-ni prototipini olish

Object.setPrototypeOf(rabbit, {}); // rabbit prototipini {} ga o'zgartirish

Object.create ixtiyoriy ikkinchi argumentga ega: xususiyat tavsiflovchilari. U yerda yangi obyektga qo’shimcha xususiyatlarni taqdim etishimiz mumkin, masalan:

let animal = {
  eats: true,
};

let rabbit = Object.create(animal, {
  jumps: {
    value: true,
  },
});

alert(rabbit.jumps); // true

Deskriptorlar Xususiyat bayroqlari va tavsiflovchilar bobida tasvirlanganidek bir xil formatda bo’ladi.

Obyektni klonlashni for..in da nusxalash xususiyatlaridan ko’ra kuchliroq bajarish uchun Object.create dan foydalanishimiz mumkin:

// obj ning to'liq bir xil sayoz kloni
let clone = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

Ushbu chaqiruv obj ning barcha nusxalarini, shu jumladan, barcha xususiyatlarni: ro’yxatga olinadigan va sanab bo’lmaydigan ma’lumotlarni, ma’lumotlar xususiyatlarini va getter/setter-larni – hamma narsani va [[Prototype]] huquqini oladi.

Qisqa tarix

Agar biz [[Prototype]] ni boshqarishning barcha usullarini hisoblasak, juda ko’p narsa bor! Buni bajarishning ko’plab usullari mavjud!

Nega shunday?

Bu tarixiy sabablarga ko’ra.

  • Konstruktor funktsiyasining "prototype" xususiyati juda qadim zamonlardan beri ishlaydi.
  • Keyinchalik 2012 yilda: Object.create standartda paydo bo’ldi. Bu berilgan prototip bilan obyekt yaratishga imkon berdi, lekin uni olishga/o’rnatishga imkon bermadi. Shunday qilib brauzerlar istalgan vaqtda prototipni olish/o’rnatishga imkon beradigan nostandart __proto__ kiruvchini qo’lladilar.
  • Keyinchalik 2015 yilda: Object.setPrototypeOf va Object.getPrototypeOf standartga qo’shildi. __proto__ hamma joyda amalga oshirildi, shuning uchun brauzerdan tashqari muhit uchun ixtiyoriy bo’lgan standartning B ilovasiga yo’l oldi.

Hozirda bizda bu usullarning barchasi bizning ixtiyorimizda.

Nima uchun __proto__ funktsiyalar bilan almashtirildi? Bu qiziq savol, bizdan nima uchun __proto__ yomon ekanligini tushunishni talab qiladi. Javobni olish uchun o’qing.

Tezlik muhim bo’lmasa, [[Prototype]]ni qayta tiklamang

Texnik jihatdan[[Prototype]]ni istalgan vaqtda olishimiz/o’rnatishimiz mumkin. Ammo, odatda, biz uni obyektni yaratish vaqtida faqat bir marta o’rnatamiz, keyin o’zgartirmaymiz:rabbit animal dan meros bo’lib qoladi va bu o’zgarmaydi.

Va JavaScript interpretatori bunga juda moslashtirilgan. Object.setPrototypeOf yoki obj.__proto__= bilan prototipni “bajarilish paytida” o’zgartirish juda sekin operatsiya bo’lib, u obyekt xususiyatlariga kirish operatsiyalari uchun ichki optimallashtirishni buzadi. Agar nima qilayotganingizni bilmasangiz yoki JavaScript-ning tezligi umuman siz uchun ahamiyatli bo’lmasa, unda bundan qochib qutuling.

“Juda oddiy” obyektlar

Ma’lumki, obyektlar kalit/qiymat juftligini saqlash uchun assotsiativ massiv sifatida ishlatilishi mumkin.

…Ammo unda foydalanuvchi tomonidan taqdim etilgan kalitlarni saqlashga harakat qilsak (masalan, foydalanuvchi tomonidan kiritilgan lug’at), biz qiziqarli nosozlikni ko’rishimiz mumkin: "__proto__" dan tashqari barcha kalitlar yaxshi ishlaydi.

Misolni tekshiring:

let obj = {};

let key = prompt("Kalit nima??", "__proto__");
obj[key] = "some value";

alert(obj[key]); // [object Object], not "some value"!

Agar foydalanuvchi __proto__ ni yozsa, tayinlash e’tiborga olinmaydi!

Bu bizni ajablantirmasligi kerak. __proto__ xususiyati alohida: u obyekt yoki null bo’lishi kerak, matn prototipga aylana olmaydi.

Ammo biz bunday xatti-harakatni amalga oshirishni xohlamaymiz, to’g’rimi? Biz kalit/qiymat juftlarini saqlamoqchimiz, va "__proto__" nomli kalit to’g’ri saqlanmadi. Demak bu xato!

Bu yerda yakunalar dahshatli emas. Ammo boshqa hollarda prototip haqiqatan ham o’zgartirilishi mumkin, shuning uchun ijro umuman kutilmagan yo’llar bilan noto’g’ri ketishi mumkin.

Eng yomoni – odatda ishlab chiquvchilar bunday imkoniyat haqida umuman o’ylamaydilar. Bu bunday xatolarni sezishni qiyinlashtiradi va hatto ularni zaif tomonlarga aylantiradi, ayniqsa JavaScript server tomonida ishlatilganda.

toString xususiyatiga kirishda kutilmagan holatlar yuz berishi mumkin – bu sukut bo’yicha funktsiya va boshqa o’rnatilgan xususiyatlar.

Muammodan qanday qochish kerak?

Birinchidan, biz Map dan foydalanishga o’tishimiz mumkin, keyin hamma narsa yaxshi.

Ammo Object ham bu yerda bizga yaxshi xizmat qilishi mumkin, chunki til yaratuvchilari bu muammo haqida juda oldin o’ylab ko’rishgan.

__proto__ bu obyektning xususiyati emas, balki Object.prototype ga kiruvchi xususiyatdir:

Shunday qilib, agar obj.__proto__ o’qilgan yoki o’rnatilgan bo’lsa, mos keladigan getter/setter uning prototipidan chaqiriladi va u oladi/o’rnatadi [[Prototype]].

Ushbu o’quv bo’limining boshida aytilganidek: __proto__ bu [[Prototype]] ga kirishning bir usuli, bu [[Prototype]] ning o’zi emas.

Endi biz obyektni assotsiativ massiv sifatida ishlatmoqchi bo’lsak, buni biroz hiyla bilan amalga oshirishimiz mumkin:

let obj = Object.create(null);

let key = prompt("Kalit nima??", "__proto__");
obj[key] = "some value";

alert(obj[key]); // "some value"

Object.create(null) prototipsiz bo’sh obyektni yaratadi ([[Prototype]] null):

Shunday qilib, __proto__ uchun getter/setter yo’q. Endi u odatdagi ma’lumotlar xususiyati sifatida qayta ishlanadi, shuning uchun yuqoridagi misol to’g’ri ishlaydi.

Bunday obyektni “juda oddiy” yoki “sof lug’at obyektlari” deb atashimiz mumkin, chunki ular oddiy obyektga qaraganda oddiyroq {...}.

Salbiy tomoni shundaki, bunday obyektlarda o’rnatilgan obyekt usullari mavjud emas, masalan, toString:

let obj = Object.create(null);

alert(obj); // Error (no toString)

…Ammo bu odatda assotsiativ massivlar uchun yaxshi.

Iltimos, e’tibor bering, obyekt bilan bog’liq usullarning aksariyati Object.something(...), masalan Object.keys(obj) – ular prototipda yo’q, shuning uchun ular bunday obyektlarda ishlashni davom ettiradi:

let chineseDictionary = Object.create(null);
chineseDictionary.hello = "你好";
chineseDictionary.bye = "再见";

alert(Object.keys(chineseDictionary)); // hello,bye

Xulosa

Prototipni o’rnatish va to’g’ridan-to’g’ri kirish uchun zamonaviy usullar:

Obyektga foydalanuvchi tomonidan yaratilgan kalitlarni qo’yishni xohlasak, o’rnatilgan __proto__ getter/setter xavfli. Agar foydalanuvchi kalit sifatida __proto__ ni kiritishi mumkinligi sababli, umid qilamanki oson, ammo umuman kutilmagan oqibatlarga olib keladigan xato bo’ladi.

Shunday qilib, biz “juda oddiy” obyektni yaratish uchun Object.create(null) dan foydalanishingiz yoki __proto__ holda yoki Map moslamalarini qo’llashimiz mumkin.

Shuningdek, Object.create obyektni barcha tavsiflovchilar bilan sayoz nusxalashning oson usulini taqdim etadi:

let clone = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

We also made it clear that __proto__ is a getter/setter for [[Prototype]] and resides in Object.prototype, just like other methods.

Shuningdek, biz __proto__ – bu [[Prototype]] uchun getter/setter ekanligini va boshqa usullar singari Object.prototype da joylashganligini ham aniqladik.

Biz Object.create(null) prototipisiz obyektni yaratishimiz mumkin. Bunday obyektlar “sof lug’atlar” sifatida ishlatiladi, ularning kalitlari sifatida "__proto __" bilan bog’liq muammolar yo’q.

Obyekt xususiyatlarini qaytaradigan barcha usullar (Object.keys va boshqalar kabi) – “o’z” xususiyatlarini qaytaradi. Agar biz merosxo’rlarni xohlasak, unda for..in dan foydalanishimiz mumkin.

Vazifalar

Har qanday key/value juftligini saqlash uchun Object.create(null) shaklida yaratilgan lug'at obyekti mavjud.

Unga dictionary.toString() usulini qo’shing, bu kalitlarni vergul bilan ajratilgan ro’yxatini qaytarishi kerak. Sizning toString obyekt uchun for..in ko’rinishida bo’lmasligi kerak.

Bu shunday ishlashi kerak:

let dictionary = Object.create(null);

// dictionary.toString usulini qo'shish uchun sizning kodingiz

// ba'zi ma'lumotlarni qo'shing
dictionary.apple = "Apple";
dictionary.__proto__ = "test"; // __proto__ bu yerda odatiy xususiyat kalitidir

// faqat apple va __proto__ tsiklda
for(let key in dictionary) {
  alert(key); // "apple", so'ng "__proto__"
}

// sizning toString amalda
alert(dictionary); // "apple,__proto__"

Usul Object.keys yordamida barcha ro’yxatga olinadigan kalitlarni olishi va ularning ro’yxatini chiqarishi mumkin.

toString ni sanoqsiz qilish uchun, uni xususiyat tavsiflovchisi yordamida aniqlaymiz. Object.create sintaksisi bizni obyektni ikkinchi argument sifatida xususiyat tavsiflovchilari bilan ta’minlashga imkon beradi.

let dictionary = Object.create(null, {
  toString: { // toString xususiyatini aniqlang
    value() { // qiymati funktsiya
      return Object.keys(this).join();
    }
  }
});

dictionary.apple = "Apple";
dictionary.__proto__ = "test";

// apple va __proto__ tsiklda
for(let key in dictionary) {
  alert(key); // "apple", so'ng "__proto__"
}

// toString tomonidan vergul bilan ajratilgan xususiyatlar ro'yxati
alert(dictionary); // "apple,__proto__"

Agar xususiyatni deskriptor yordamida yaratadigan bo’lsak, uning bayroqlari sukut bo’yicha false bo’ladi. Shunday qilib, yuqoridagi kodda dictionary.toString ni sanab bo’lmaydi.

Ko’rib chiqish uchun Xususiyat bayroqlari va tavsiflovchilar bo’limiga qarang.

Keling, yangi rabbit obyekti yarataylik:

function Rabbit(name) {
  this.name = name;
}
Rabbit.prototype.sayHi = function () {
  alert(this.name);
};

let rabbit = new Rabbit("Rabbit");

Ushbu chaqiruvlar xuddi shu narsani qiladimi yoki yo’qmi?

rabbit.sayHi();
Rabbit.prototype.sayHi();
Object.getPrototypeOf(rabbit).sayHi();
rabbit.__proto__.sayHi();

Birinchi chaqiruvda this == rabbit, boshqalarida this Rabbit.prototype ga teng, chunki bu aslida nuqta oldidagi obyekt.

Shunday qilib, faqat birinchi chaqiruv Rabbit, boshqalari undefined ni ko’rsatadi:

function Rabbit(name) {
  this.name = name;
}
Rabbit.prototype.sayHi = function () {
  alert(this.name);
};

let rabbit = new Rabbit("Rabbit");

rabbit.sayHi(); // Rabbit
Rabbit.prototype.sayHi(); // undefined
Object.getPrototypeOf(rabbit).sayHi(); // undefined
rabbit.__proto__.sayHi(); // undefined
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…)