OOP 4 ustuni (Encapsulation, Abstraction, Inheritance, Polymorphism), SOLID printsiplari, prototypal vs classical OOP, composition vs inheritance, va duck typing haqida interview savollari.
Javob
OOP 4 ta ustun (pillar) ga qurilgan:
- Encapsulation — ma'lumotni yashirish va himoyalash
- Abstraction — murakkablikni yashirish, faqat kerakli qismini ko'rsatish
- Inheritance — bir class'dan boshqasiga meros olish (code reuse)
- Polymorphism — bir xil nomli method turli class'larda turli xil ishlashi
// 1. Encapsulation — private field bilan himoya:
class BankAccount {
#balance = 0;
deposit(amount) {
if (amount > 0) this.#balance += amount;
}
get balance() { return this.#balance; }
}
// 2. Abstraction — foydalanuvchi faqat play() ni biladi, ichki dekoder mexanizmi yashirin:
class AudioDecoder {
decode(file) { /* ... murakkab decoding logic ... */ return "decoded audio"; }
}
class MediaPlayer {
#decoder = new AudioDecoder();
play(file) { return this.#decoder.decode(file); }
// Foydalanuvchi #decoder, decode() haqida bilmasligi kerak — faqat play()
}
// 3. Inheritance — meros:
class Animal {
speak() { return "..."; }
}
class Dog extends Animal {
speak() { return "Hav!"; }
}
// 4. Polymorphism — turli xil xatti-harakat:
const animals = [new Animal(), new Dog()];
animals.forEach(a => console.log(a.speak()));
// "..." , "Hav!" — bir xil method, turli natijaJavob
Encapsulation — ob'ektning ichki state'ini tashqi koddan berkitish va faqat belgilangan method'lar orqali kirish imkonini berish.
JS da encapsulation qilishning asosiy usullari:
// 1. Private fields # (zamonaviy, eng yaxshi usul):
class User {
#password;
constructor(name, pass) {
this.name = name;
this.#password = pass;
}
checkPassword(pass) { return pass === this.#password; }
}
const u = new User("Ali", "secret");
// u.#password; // ❌ SyntaxError — tashqi kirish taqiq
// 2. Closures (ES6 dan oldingi pattern):
function createCounter() {
let count = 0; // private — tashqaridan ko'rinmaydi
return {
increment() { count++; },
getCount() { return count; }
};
}
// 3. WeakMap:
const _data = new WeakMap();
class Wallet {
constructor(balance) { _data.set(this, { balance }); }
getBalance() { return _data.get(this).balance; }
}Muhim: _ prefix (underscore convention) encapsulation emas — bu faqat kelishuv, hech qanday himoya bermaydi. # private fields esa til darajasidagi enforcement.
Javob
Ko'pchilik bu ikkisini aralashtirib yuboradi. Farqi:
| Encapsulation | Abstraction | |
|---|---|---|
| Maqsad | Ma'lumotni himoyalash | Murakkablikni yashirish |
| Savol | "Kim kirishi mumkin?" | "Nimani ko'rsatish kerak?" |
| Vosita | Private fields, closures | Public API, facade |
| Yo'nalish | Ichki state ni berkitish | Tashqi interface ni soddalashtirish |
class Car {
// Encapsulation — ichki state yashirin:
#engine;
#fuel = 100;
constructor(engine) {
this.#engine = engine;
}
// Abstraction — foydalanuvchi faqat shu method'larni biladi:
start() {
this.#checkFuel(); // murakkab ichki logika
this.#engine.ignite(); // tashqi dependency
this.#warmUp(); // ichki jarayon
return "Mashina yurdi"; // sodda natija
}
// Ichki murakkablik — yashirin:
#checkFuel() {
if (this.#fuel <= 0) throw new Error("Yonilg'i yo'q");
}
#warmUp() {
// 50 qatorlik murakkab logika...
}
}
// Foydalanuvchi faqat start() ni biladi — ichki 3 ta qadam haqida bilmasligi kerak
// Bu ABSTRACTION
// #engine, #fuel ga kira olmasligi — bu ENCAPSULATIONJavob
Polymorphism — "ko'p shakllilik". JS da 3 turi bor:
1. Method Overriding — child class parent method ni qayta yozadi:
class Shape {
area() { return 0; }
}
class Circle extends Shape {
constructor(r) { super(); this.r = r; }
area() { return Math.PI * this.r ** 2; } // override
}
class Square extends Shape {
constructor(s) { super(); this.s = s; }
area() { return this.s ** 2; } // override
}
// Polymorphism — bir xil method chaqiruv, turli natija:
[new Circle(5), new Square(3)].forEach(s => console.log(s.area()));
// 78.54, 92. Duck Typing — type emas, behavior tekshiriladi:
// Bu ob'ektlar hech qanday umumiy class'dan meros olmagan
const file = { read() { return "file content"; } };
const stream = { read() { return "stream data"; } };
function processInput(source) {
return source.read(); // read() bor — yetarli
}
processInput(file); // ishlaydi
processInput(stream); // ishlaydi3. Method Overloading (emulatsiya) — JS da native yo'q:
class Formatter {
format(value) {
if (typeof value === "number") return value.toFixed(2);
if (typeof value === "string") return value.trim();
if (Array.isArray(value)) return value.join(", ");
return String(value);
}
}Javob
S — Single Responsibility Principle (SRP): Har bir class bitta vazifa uchun javobgar. O'zgartirish uchun bitta sabab.
// ❌ SRP buzilgan:
class UserService {
createUser(data) { /* user yaratish */ }
sendWelcomeEmail(user) { /* email */ }
generateAvatar(user) { /* rasm */ }
logAction(action) { /* log */ }
}
// ✅ SRP to'g'ri:
class UserService { createUser(data) { /* ... */ } }
class EmailService { send(to, template) { /* ... */ } }
class AvatarService { generate(user) { /* ... */ } }
class Logger { log(action) { /* ... */ } }O — Open/Closed Principle (OCP): Kengaytirishga ochiq, o'zgartirishga yopiq.
// ❌ OCP buzilgan — har yangi tur uchun if qo'shish kerak:
class Discount {
calculate(type, price) {
if (type === "student") return price * 0.8;
if (type === "veteran") return price * 0.7;
// yangi tur = kodni o'zgartirish ⚠️
}
}
// ✅ OCP to'g'ri — yangi strategy qo'shish yetarli:
class StudentDiscount {
calculate(price) { return price * 0.8; }
}
class VeteranDiscount {
calculate(price) { return price * 0.7; }
}
class PriceCalculator {
constructor(discountStrategy) {
this.discount = discountStrategy;
}
getPrice(price) {
return this.discount.calculate(price);
}
}
// Yangi tur — faqat yangi class, mavjud kod o'zgarmaydi:
class EmployeeDiscount {
calculate(price) { return price * 0.75; }
}Javob
LSP: child class parent o'rnida ishlatilganda dastur buzilmasligi kerak. Ya'ni parent bilan ishlaydigan har qanday kod child bilan ham to'g'ri ishlashi shart.
// ❌ LSP buzilgan — klassik Rectangle/Square muammosi:
class Rectangle {
constructor(w, h) {
this.width = w;
this.height = h;
}
setWidth(w) { this.width = w; }
setHeight(h) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w) {
this.width = w;
this.height = w; // ❌ height ham o'zgaradi — kutilmagan!
}
setHeight(h) {
this.width = h; // ❌ width ham o'zgaradi
this.height = h;
}
}
// Bu funksiya Rectangle bilan to'g'ri ishlaydi:
function testArea(rect) {
rect.setWidth(5);
rect.setHeight(4);
console.log(rect.area()); // 20 kutamiz
}
testArea(new Rectangle(0, 0)); // 20 ✅
testArea(new Square(0, 0)); // 16 ❌ — LSP buzildi!
// Square berganда width = 4 (setHeight height ham width o'zgartirdi)
// ✅ LSP to'g'ri yechim:
class Shape {
area() { throw new Error("implement qiling"); }
}
class Rectangle extends Shape {
constructor(w, h) { super(); this.w = w; this.h = h; }
area() { return this.w * this.h; }
}
class Square extends Shape {
constructor(side) { super(); this.side = side; }
area() { return this.side ** 2; }
}
// Endi ikkalasi ham Shape dan meros, bir-biriga bog'liq emasJavob
| Inheritance | Composition | |
|---|---|---|
| Munosabat | "is-a" (It is a Hayvon) | "has-a" (Mashina has Motor) |
| Bog'liqlik | Qattiq (tight coupling) | Sust (loose coupling) |
| Flexibility | Kam — hierarxiya qotib qoladi | Yuqori — istalgan vaqtda o'zgaradi |
| Qachon | Aniq "is-a" bo'lganda, 2-3 daraja | Ko'pchilik holatda |
// ❌ Inheritance xato ishlatilgan:
class Stack extends Array {
peek() { return this.at(-1); }
}
const s = new Stack();
s.push(1, 2, 3);
s.shift(); // ❌ Stack da shift bo'lmasligi kerak!
s.splice(0, 1); // ❌ Stack da splice bo'lmasligi kerak!
// Stack IS-A Array emas!
// ✅ Composition to'g'ri:
class Stack {
#items = [];
push(item) { this.#items.push(item); }
pop() { return this.#items.pop(); }
peek() { return this.#items.at(-1); }
get size() { return this.#items.length; }
// splice, shift, unshift — yo'q! Faqat stack operatsiyalari
}Qoida: "Agar shubha bo'lsa — composition tanlang." Inheritance faqat aniq is-a munosabat bo'lganda va hierarxiya sayoz (2-3 daraja) bo'lganda.
Javob
Duck typing — "Agar o'rdakdek yursa va o'rdakdek qaqillasa — u o'rdak." Ya'ni ob'ektning type i emas, behavior i (qanday method'lari bor) muhim.
JS dinamik tipli til bo'lgani uchun duck typing natural ishlaydi:
// Iterator protocol — duck typing'ning klassik misoli:
// "Agar ob'ektda Symbol.iterator bor — u iterable"
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
for (const n of range) console.log(n); // 1, 2, 3, 4, 5
// range Array emas, Set emas — lekin iterable!
// Thenable — Promise.resolve duck typing ishlatadi:
const thenable = {
then(resolve) { resolve(42); }
};
Promise.resolve(thenable).then(console.log); // 42
// Bu Promise emas, lekin then() bor — Promise dek ishlatildi!Afzalligi: Interfeyslarsiz flexibility. Kamchiligi: Compile-time xato yo'q — runtime da sinadi. TypeScript bu muammoni yechadi.
Javob
JS da abstract keyword yo'q. Lekin new.target va Error throw qilish bilan emulatsiya qilish mumkin:
class DataStore {
constructor() {
if (new.target === DataStore) {
throw new Error("DataStore abstract — to'g'ridan yaratib bo'lmaydi");
}
}
// Abstract method'lar — subclass implement qilishi SHART:
save(key, value) {
throw new Error(`${this.constructor.name}.save() implement qilinmagan`);
}
get(key) {
throw new Error(`${this.constructor.name}.get() implement qilinmagan`);
}
// Concrete method — barcha subclass'larga umumiy:
has(key) {
return this.get(key) !== undefined;
}
}
class MemoryStore extends DataStore {
#data = new Map();
save(key, value) { this.#data.set(key, value); }
get(key) { return this.#data.get(key); }
}
class LocalStore extends DataStore {
save(key, value) { localStorage.setItem(key, JSON.stringify(value)); }
get(key) { return JSON.parse(localStorage.getItem(key)); }
}
// const ds = new DataStore(); // ❌ Error: abstract
const mem = new MemoryStore(); // ✅
mem.save("name", "Ali");
console.log(mem.has("name")); // true — concrete method ishlaydinew.target — constructor qaysi class uchun chaqirilganini ko'rsatadi. Agar DataStore o'zi uchun chaqirilsa — xato beradi.
Javob
DIP: Yuqori darajadagi modul pastki darajadagiga bevosita bog'lanmasligi kerak. Ikkalasi ham abstraktsiyaga bog'lanishi kerak. JS da bu dependency injection orqali amalga oshiriladi:
// ❌ DIP buzilgan — PaymentService to'g'ridan-to'g'ri Stripe ga bog'liq:
class PaymentService {
constructor() {
this.provider = new StripeProvider(); // qattiq bog'liqlik
}
charge(amount) {
return this.provider.charge(amount);
}
}
// PayPal ga o'tkazmoqchi bo'lsak — PaymentService kodini o'zgartirish kerak
// ✅ DIP to'g'ri — dependency injection:
// "Interface" (duck typing):
// Har qanday provider: { charge(amount), refund(id) }
class StripeProvider {
charge(amount) { return `Stripe: $${amount} charged`; }
refund(id) { return `Stripe: ${id} refunded`; }
}
class PayPalProvider {
charge(amount) { return `PayPal: $${amount} charged`; }
refund(id) { return `PayPal: ${id} refunded`; }
}
class PaymentService {
constructor(provider) { // tashqaridan beriladi
this.provider = provider;
}
charge(amount) { return this.provider.charge(amount); }
refund(id) { return this.provider.refund(id); }
}
// Istalgan provider bilan ishlaydi — kodni o'zgartirmasdan:
const stripe = new PaymentService(new StripeProvider());
const paypal = new PaymentService(new PayPalProvider());
// Test uchun mock:
const mock = new PaymentService({ charge: () => "mock", refund: () => "mock" });DIP ning afzalligi:
- Testability — mock inject qilish oson
- Flexibility — provider almashtirinsh bir qator
- Maintainability — provider o'zgarganda PaymentService o'zgarmaydi
Deep Dive:
JavaScript da DIP static type system yo'qligi uchun duck typing orqali amalga oshiriladi — interface spec'da aniqlanmagan, lekin runtime'da provider.charge method mavjudligi tekshiriladi. TypeScript qo'shilganda interface PaymentProvider { charge(amount: number): string } kabi compile-time enforcement qo'shiladi. V8 nuqtai nazaridan, agar turli provider'lar bir xil method signature'ga ega bo'lsa — inline cache polymorphic holatda ishlaydi (2-4 ta shape), lekin monomorphic'dan sekinroq.
Javob
// Classical OOP (Java, C#):
// Class = blueprint (shablon). Instance = nusxa.
// Class yaratilgandan keyin o'zgarmaydi.
// Prototypal OOP (JavaScript):
// Object = prototype (tirik ob'ekt). Instance = prototype dan meros.
// Istalgan vaqtda o'zgartirish mumkin.
const animal = {
speak() { return `${this.name} gapirdi`; }
};
// Object.create — ob'ektdan ob'ektga bevosita meros:
const dog = Object.create(animal);
dog.name = "Rex";
dog.bark = function() { return "Hav!"; };
console.log(dog.speak()); // "Rex gapirdi" — animal dan meros
console.log(dog.bark()); // "Hav!" — o'ziniki
// Runtime da prototype o'zgartirish:
animal.eat = function() { return `${this.name} yeyapti`; };
console.log(dog.eat()); // "Rex yeyapti" — darhol ishlaydi!
// Classical OOP da bu MUMKIN EMAS
// ES6 class — prototypal OOP ustiga syntactic sugar:
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} gapirdi`; }
}
// Ichida xuddi shu prototype mexanizmi ishlaydi
typeof Animal; // "function" — class emas!| Classical | Prototypal (JS) | |
|---|---|---|
| Blueprint | Class (abstract shablon) | Prototype (tirik ob'ekt) |
| Meros | Class → class | Object → object |
| Runtime o'zgartirish | Yo'q | Ha |
| Abstract/Interface | Til darajasida | Pattern bilan |
Javob
Mixin — bir nechta manba'dan xatti-harakatlarni aralashtirib olish usuli. JS da multiple inheritance yo'q (extends faqat bitta class dan). Mixin bu muammoni yechadi:
// Mixin'lar — funksiya sifatida:
const Serializable = (Base) => class extends Base {
toJSON() {
return JSON.stringify(this);
}
static fromJSON(json) {
return Object.assign(new this(), JSON.parse(json));
}
};
const Validatable = (Base) => class extends Base {
validate() {
for (const [key, value] of Object.entries(this)) {
if (value === null || value === undefined) {
throw new Error(`${key} bo'sh bo'lmasligi kerak`);
}
}
return true;
}
};
const Timestamped = (Base) => class extends Base {
constructor(...args) {
super(...args);
this.createdAt = new Date();
this.updatedAt = new Date();
}
touch() { this.updatedAt = new Date(); }
};
// Kerakli mixin'larni "aralashtirib" olish:
class User extends Timestamped(Validatable(Serializable(Object))) {
constructor(name, email) {
super();
this.name = name;
this.email = email;
}
}
const user = new User("Ali", "ali@mail.com");
user.validate(); // ✅ Validatable dan
console.log(user.toJSON()); // Serializable dan
user.touch(); // Timestamped danPrototype chain: user → User.prototype → Timestamped → Validatable → Serializable → Object.prototype
Javob
JS multi-paradigm — ikkalasini ham qo'llab-quvvatlaydi. Tanlash kontekstga bog'liq:
// OOP yaxshi ishlaydi — domain modeling:
class Order {
#items = [];
#status = "pending";
addItem(item) { this.#items.push(item); }
submit() {
if (this.#items.length === 0) throw new Error("Bo'sh buyurtma");
this.#status = "submitted";
}
get total() { return this.#items.reduce((s, i) => s + i.price, 0); }
}
// FP yaxshi ishlaydi — data transformation:
const processOrders = (orders) =>
orders
.filter(o => o.status === "submitted")
.map(o => ({ ...o, tax: o.total * 0.12 }))
.sort((a, b) => b.total - a.total);| Shart | OOP | FP |
|---|---|---|
| Murakkab domain (e-commerce, game) | ✅ | |
| State boshqarish (UI, session) | ✅ | |
| Data transformation, pipeline | ✅ | |
| Pure, testable logika | ✅ | |
| API/service layer | ✅ | |
| Utility funksiyalar | ✅ |
Amalda: React = FP (functional components) + OOP (class services). Node.js = OOP (controllers, models) + FP (middleware, data processing).
Deep Dive:
ECMAScript spec o'zi multi-paradigm dizayn qilingan: function'lar first-class object ([[Call]] internal method), class syntactic sugar, Object.create prototypal delegation. V8 engine ikki paradigmani ham bir xil samarali optimize qiladi — pure function'lar TurboFan'da inline bo'ladi, class method'lar prototype sharing orqali memory tejaydi. JavaScript'ning duck typing xususiyati FP va OOP ni erkin aralashtirishga imkon beradi — TypeScript'ning structural typing'i ham shu falsafani davom ettiradi.
Javob
Delegation — ob'ekt o'zi bajara olmaydigan ishni boshqa ob'ektga topshiradi (forward qiladi). Inheritance'dan farqi — bu "is-a" munosabat emas, balki "handles-by-forwarding".
// Inheritance — Dog IS an Animal:
class Animal {
eat() { return "yeyapman"; }
}
class Dog extends Animal {
bark() { return "hav!"; }
}
// Dog.prototype.__proto__ === Animal.prototype
// Method Dog ichida — prototype chain orqali topiladi
// Delegation — UserService DELEGATES to Logger:
class Logger {
log(msg) { console.log(`[LOG] ${msg}`); }
}
class UserService {
#logger;
constructor(logger) { this.#logger = logger; }
createUser(name) {
// O'zi log qilishni bilmaydi — Logger'ga TOPSHIRADI:
this.#logger.log(`User yaratildi: ${name}`);
return { name };
}
}| Inheritance | Delegation | |
|---|---|---|
| Munosabat | is-a | uses / forwards-to |
| Bog'liqlik | Qattiq (compile-time) | Sust (runtime o'zgaradi) |
| Almashtirishlik | Qiyin | Oson (dependency injection) |
| JS'da | extends / prototype |
Forwarding / composition |
OLOO (Kyle Simpson) — JS prototypal delegation:
const Validator = {
validate() {
return this.value != null && this.value !== "";
}
};
const EmailField = Object.create(Validator);
EmailField.init = function(value) {
this.value = value;
return this;
};
EmailField.isValidEmail = function() {
return this.validate() && this.value.includes("@");
};
const field = Object.create(EmailField).init("ali@mail.com");
console.log(field.isValidEmail()); // true
// field → EmailField → Validator — delegation chainJavob
Law of Demeter (LoD) — ob'ekt faqat yaqin do'stlari bilan gaplashishi kerak. "Begonalar" ning ichki tuzilishiga kirmaslik kerak. Yuqoridagi kodda order → customer → address → city — 3 daraja chuqur, bu "train wreck" deyiladi.
// ❌ Muammo: address strukturasi o'zgarsa — SHU KOD SINADI
// Masalan: address → location ga qayta nomlansa, yoki address nested bo'lsa
// ✅ Law of Demeter — har bir ob'ekt o'z ma'lumotini o'zi beradi:
class Address {
#city;
constructor(city) { this.#city = city; }
getCity() { return this.#city; }
}
class Customer {
#address;
#membership;
constructor(address, membership) {
this.#address = address;
this.#membership = membership;
}
getCity() { return this.#address.getCity(); } // delegatsiya
isGold() { return this.#membership.tier === "gold"; } // delegatsiya
}
class Order {
#customer;
#total;
constructor(customer, total) {
this.#customer = customer;
this.#total = total;
}
getShippingCity() {
return this.#customer.getCity(); // faqat 1 daraja — yaqin do'st
}
applyDiscount() {
if (this.#customer.isGold()) { // faqat 1 daraja
this.#total *= 0.9;
}
}
}Qoida: Agar method ichida 2+ ta dot (.) zanjiri boshqa ob'ektlarga kirayotgan bo'lsa — LoD buzilayotgan bo'lishi mumkin. (builder.setA().setB() bundan mustasno — bu method chaining, bitta ob'ekt.)
Javob
Tell Don't Ask — ob'ektdan ma'lumot so'ramang va uning o'rniga qaror qabul qilmang. Buning o'rniga ob'ektga nima qilish kerakligini ayting — u o'zi qaror qilsin.
// ❌ Ask — so'rab, keyin tashqarida qaror qilish (procedural):
function shipOrder(order) {
const status = order.getStatus();
const items = order.getItems();
if (status === "paid" && items.length > 0) {
const weight = items.reduce((s, i) => s + i.weight, 0);
if (weight > 30) {
order.setShipping("freight");
} else {
order.setShipping("standard");
}
order.setStatus("shipped");
}
}
// Business logika TASHQARIDA — Order o'zi haqida hech narsa bilmaydi
// ✅ Tell — ob'ektga aytish:
class Order {
#status;
#items;
#shipping;
ship() {
if (this.#status !== "paid" || this.#items.length === 0) {
throw new Error("Buyurtma jo'natishga tayyor emas");
}
this.#shipping = this.#totalWeight > 30 ? "freight" : "standard";
this.#status = "shipped";
}
// Private getter (ES2022+) — private field + private method bilan birga qabul qilingan
get #totalWeight() {
return this.#items.reduce((s, i) => s + i.weight, 0);
}
}
order.ship(); // ✅ Tell — nima qilishni aytdik, qanday qilishni Order o'zi biladiAfzalliklari:
- Business logika bitta joyda — class ichida
- Encapsulation saqlanadi — ichki state tashqariga chiqmaydi
- Qaror logikasi takrorlanmaydi (DRY)
Javob
Coupling — modullar o'rtasidagi bog'liqlik darajasi. Cohesion — modul ichidagi elementlarning bir-biriga aloqadorligi.
Yaxshi dizayn = Loose Coupling + High Cohesion.
// ❌ Tight Coupling + Low Cohesion:
class UserManager {
createUser(name) {
const id = db.query("INSERT INTO users..."); // ❌ DB ga qattiq bog'liq
smtp.send(`user-${id}@app.com`, "Welcome!"); // ❌ SMTP ga qattiq bog'liq
redis.set(`session:${id}`, "{}"); // ❌ Redis ga qattiq bog'liq
// Low cohesion: user + email + session = 3 xil soha
}
}
// ✅ Loose Coupling + High Cohesion:
class UserService {
// High cohesion: faqat user operatsiyalari
#repo; #events;
constructor(repo, events) { // Loose coupling: dependency injection
this.#repo = repo;
this.#events = events;
}
createUser(name) {
const user = this.#repo.save({ name });
this.#events.emit("user:created", user); // qolganlari event handler qiladi
return user;
}
}| Yomon | Yaxshi | |
|---|---|---|
| Coupling | Tight — to'g'ridan-to'g'ri new, global, ichki field |
Loose — interface, DI, events |
| Cohesion | Low — aralash vazifalar bir class da | High — bir soha, bir class |
Javob
instanceof operator ichida Symbol.hasInstance static method chaqiriladi. Uni override qilib, instanceof xatti-harakatini butunlay o'zgartirish mumkin:
// ✅ Interface tekshiruvchi — duck typing bilan birga:
class Stringable {
static [Symbol.hasInstance](instance) {
return instance != null && typeof instance.toString === "function"
&& instance.toString !== Object.prototype.toString;
}
}
class User {
constructor(name) { this.name = name; }
toString() { return `User: ${this.name}`; }
}
console.log(new User("Ali") instanceof Stringable); // true — custom toString bor
console.log("hello" instanceof Stringable); // true — String.toString
console.log({} instanceof Stringable); // false — default toString
console.log(42 instanceof Stringable); // true — Number.prototype.toString !== Object.prototype.toString
// ✅ Array-like tekshirish:
class ArrayLike {
static [Symbol.hasInstance](instance) {
return instance != null
&& typeof instance.length === "number"
&& Number.isInteger(instance.length)
&& instance.length >= 0;
}
}
console.log([1, 2] instanceof ArrayLike); // true
console.log("hello" instanceof ArrayLike); // true (length = 5)
console.log({ length: 3 } instanceof ArrayLike); // true
console.log({ length: -1 } instanceof ArrayLike); // falseUnder the hood: x instanceof Foo → engine Foo[Symbol.hasInstance](x) ni chaqiradi. Default implementatsiya prototype chain bo'ylab yuradi, lekin static method bilan butunlay o'zgartirishingiz mumkin.
Deep Dive:
Spec'da instanceof operatori InstanceofOperator(V, target) abstract operation'ni chaqiradi. Birinchi qadam: target[@@hasInstance] bor-yo'qligini tekshiradi (GetMethod). Agar bor — uni chaqiradi va natijani ToBoolean qiladi. Agar yo'q — OrdinaryHasInstance(target, V) chaqiriladi, bu prototype chain'ni iteratsiya qiladi. Function.prototype[@@hasInstance] default implementatsiya sifatida barcha funksiyalarda mavjud va OrdinaryHasInstance ni chaqiradi.
Javob
Proxy ob'ektning fundamental operatsiyalarini (get, set, delete) tutib oladi. Bu OOP da runtime validation, access control va observable pattern'ni amalga oshiradi:
// ✅ Typed object — runtime type enforcement:
function createTyped(schema) {
return new Proxy({}, {
set(target, prop, value) {
if (!(prop in schema)) {
throw new Error(`Noma'lum property: ${prop}`);
}
const expectedType = schema[prop];
if (typeof value !== expectedType) {
throw new TypeError(
`${prop}: ${expectedType} kutildi, ${typeof value} berildi`
);
}
target[prop] = value;
return true;
}
});
}
const user = createTyped({
name: "string",
age: "number",
active: "boolean"
});
user.name = "Ali"; // ✅
user.age = 25; // ✅
// user.age = "yigirma"; // ❌ TypeError
// user.role = "admin"; // ❌ Error: noma'lum propertyProxy afzalliklari OOP da:
- Runtime validation — compile-time emas, amalda ishlaydi
- Transparent — client kodi o'zgarmaydi, proxy "yashirin"
- Flexible — istalgan vaqtda qoidalar o'zgaradi
- Cross-cutting concerns — logging, caching, access control
Batafsil: → 23-proxy-reflect.md
Deep Dive:
Spec'da Proxy object 13 ta internal method'ni ([[Get]], [[Set]], [[Has]], [[Delete]] va boshqalar) intercept qilishi mumkin — bu MakeBasicObject o'rniga ProxyCreate orqali yaratiladi. Har bir trap chaqirilganda spec invariant'lar tekshiradi — masalan, agar target'da configurable: false, writable: false property bo'lsa, [[Get]] trap boshqa qiymat qaytarsa TypeError tashlaydi. Bu Proxy'ning fundamental object semantic'larini buzishini oldini oladi.
Savol:
class Base {
constructor() {
this.init();
}
init() {
this.type = "base";
}
}
class Child extends Base {
count = 0;
init() {
this.type = "child";
this.count = 10;
}
getCount() {
return this.count;
}
}
const c = new Child();
console.log(c.type);
console.log(c.getCount());Javob
"child"
0
// Qadam-baqadam:
// 1. new Child() → Child constructor yo'q, Base constructor chaqiriladi
// 2. Base constructor: this.init() → this = Child instance
// → Child.init() chaqiriladi (polymorphism — override!)
// 3. Child.init(): this.type = "child", this.count = 10
// 4. LEKIN! — class fields (= 0) constructor body dan KEYIN EMAS
// Aslida class fields Base constructor DAN KEYIN initialize bo'ladi
// Ya'ni: super() → base constructor → KEYIN child fields initialize
//
// Tartib:
// a) Base constructor ishlaydi → this.init() → Child.init()
// → this.type = "child", this.count = 10
// b) Child class fields initialize bo'ladi → count = 0 (qayta yozildi!)
//
// Natija: type = "child", count = 0
// Sabab: Spec algoritmi:
// 1. Default derived constructor: super(...args)
// 2. Base constructor ishlaydi (this.init() → Child.init() dispatch)
// 3. Base constructor return → InitializeInstanceElements(this, Child)
// bu bosqichda class field initializer'lar source order'da ishlaydi:
// this.count = 0 ← 10 ni OVERRIDE qildi!
// 4. Child constructor body qolgani (bizda yo'q)
// Natija: type="child" (Base/Child.init chaqirilgan), count=0 (class field keyin)Xulosa: Class field initializer'lar super() dan keyin, lekin child constructor body'dan oldin InitializeInstanceElements spec operation orqali ishlaydi — bu ko'pchilikni yanglishtiradigan nuance. Qoida: parent constructor'da virtual method chaqirmang — agar chaqirsangiz, child'ning class field'lari hali initialize qilinmagan bo'lishi mumkin.
Savol:
class Bird {
fly() {
return `${this.name} uchyapti`;
}
}
class Eagle extends Bird {
constructor() { super(); this.name = "Eagle"; }
}
class Penguin extends Bird {
constructor() { super(); this.name = "Penguin"; }
fly() {
throw new Error("Penguinlar ucha olmaydi");
}
}
function releaseBirds(birds) {
return birds.map(b => b.fly());
}
releaseBirds([new Eagle(), new Penguin()]);Javob
Muammo — Liskov Substitution Principle (LSP) buzilgan. Penguin Bird o'rnida ishlatilganda fly() error tashlaydi — ya'ni parent shartnomasi buzildi.
// ✅ To'g'ri dizayn — hierarxiyani qayta qurish:
class Bird {
constructor(name) { this.name = name; }
move() {
throw new Error(`${this.constructor.name}.move() implement qilinmagan`);
}
}
class FlyingBird extends Bird {
move() { return `${this.name} uchyapti`; }
fly() { return this.move(); }
}
class SwimmingBird extends Bird {
move() { return `${this.name} suzyapti`; }
swim() { return this.move(); }
}
class Eagle extends FlyingBird {
constructor() { super("Eagle"); }
}
class Penguin extends SwimmingBird {
constructor() { super("Penguin"); }
}
function releaseBirds(birds) {
return birds.map(b => b.move()); // move() hammada bor — xavfsiz
}
releaseBirds([new Eagle(), new Penguin()]);
// ["Eagle uchyapti", "Penguin suzyapti"] ✅Savol: EventEmitter class yarating — on, off, emit, once method'lari bilan. Encapsulation va polymorphism ishlating.
Javob
class EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, []);
}
this.#listeners.get(event).push(callback);
return this; // chaining uchun
}
off(event, callback) {
const callbacks = this.#listeners.get(event);
if (!callbacks) return this;
const index = callbacks.indexOf(callback);
if (index !== -1) callbacks.splice(index, 1);
return this;
}
emit(event, ...args) {
const callbacks = this.#listeners.get(event);
if (!callbacks) return false;
for (const cb of [...callbacks]) { // copy — once o'chirish xavfsiz
cb.apply(this, args);
}
return true;
}
once(event, callback) {
const wrapper = (...args) => {
this.off(event, wrapper);
callback.apply(this, args);
};
return this.on(event, wrapper);
}
}
// Test:
const emitter = new EventEmitter();
const handler = (msg) => console.log(`Got: ${msg}`);
emitter.on("data", handler);
emitter.once("connect", () => console.log("Connected!"));
emitter.emit("connect"); // "Connected!"
emitter.emit("connect"); // (hech narsa — once!)
emitter.emit("data", "hello"); // "Got: hello"
emitter.off("data", handler);
emitter.emit("data", "world"); // (hech narsa — off qilindi)OOP printsiplari:
- Encapsulation:
#listenersprivate — tashqaridan manipulate qilib bo'lmaydi - Polymorphism:
onceichidaonvaoffni qayta ishlatadi - SRP: Faqat event boshqarish — boshqa vazifa yo'q
Deep Dive:
Node.js'ning built-in EventEmitter ichida _events property Object.create(null) bilan yaratiladi — prototype pollution'dan himoya. Listener'lar soni _maxListeners (default 10) bilan cheklanadi — oshsa MaxListenersExceededWarning beriladi (memory leak oldini olish). once implementatsiyasida Node.js onceWrapper funksiyasiga listener property qo'shadi — removeListener wrapper emas, original callback'ni topishi uchun.
Javob
Method chaining — har bir method this ni qaytaradi, shunda bir necha method'ni zanjir shaklida chaqirish mumkin. Bu pattern jQuery, Lodash, Express, va ko'plab JS kutubxonalarida ishlatiladi.
class FormValidator {
#rules = {};
#errors = [];
#data;
setData(data) {
this.#data = data;
this.#errors = [];
return this; // ← this qaytarish = chaining mumkin
}
required(field) {
if (!this.#data[field]) {
this.#errors.push(`${field} majburiy`);
}
return this;
}
minLength(field, min) {
if (this.#data[field]?.length < min) {
this.#errors.push(`${field} kamida ${min} ta belgi`);
}
return this;
}
isEmail(field) {
if (this.#data[field] && !this.#data[field].includes("@")) {
this.#errors.push(`${field} noto'g'ri email`);
}
return this;
}
validate() {
return {
isValid: this.#errors.length === 0,
errors: [...this.#errors]
};
}
}
// Fluent interface — zanjir:
const result = new FormValidator()
.setData({ name: "Al", email: "invalid", age: "" })
.required("name")
.minLength("name", 3)
.required("email")
.isEmail("email")
.required("age")
.validate();
console.log(result);
// { isValid: false, errors: ["name kamida 3 ta belgi", "email noto'g'ri email", "age majburiy"] }Muhim: return this — mutable chaining (original o'zgaradi). Immutable chaining uchun return new ClassName(...) ishlatiladi (masalan, Array.map(), String.trim()).