JavaScript Proxy handler zanjirlari yordamida ko'p darajali obyektni tutishni o'zlashtiring. Bu ichki tuzilmalarda ma'lumotlarga kirish va ularni boshqarish ustidan kuchli nazorat beradi.
JavaScript Proxy Handler Zanjiri: Ko'p darajali Obyekt Tutishni O'zlashtirish
Zamonaviy JavaScript dasturlash olamida Proxy obyekti kuchli meta-dasturlash vositasi bo'lib, dasturchilarga maqsadli obyektlardagi asosiy operatsiyalarni tutib olish va qayta belgilash imkonini beradi. Proxylarning asosiy ishlatilishi yaxshi hujjatlashtirilgan bo'lsa-da, Proxy handlerlarini zanjirlash san'atini o'zlashtirish, ayniqsa murakkab, ko'p darajali ichki obyektlar bilan ishlashda nazoratning yangi o'lchamlarini ochadi. Bu ilg'or texnika murakkab tuzilmalardagi ma'lumotlarni nozik ushlash va boshqarish imkonini beradi, reaktiv tizimlarni loyihalashda, nozik kirish nazoratini amalga oshirishda va murakkab validatsiya qoidalarini tatbiq etishda misli ko'rilmagan moslashuvchanlikni taklif qiladi.
JavaScript Proxylarining Asosini Tushunish
Handler zanjirlariga sho'ng'ishdan oldin, JavaScript Proxylarining asoslarini tushunish muhimdir. A Proxy obyekti uning konstruktoriga ikkita argumentni o'tkazish orqali yaratiladi: a target obyekti va a handler obyekti. The target – bu proxy boshqaradigan obyekt, va the handler esa proxyda bajariladigan operatsiyalar uchun maxsus xatti-harakatlarni belgilaydigan obyektdir.
The handler obyekti turli tutqichlar (traps)ni o'z ichiga oladi, ular ma'lum operatsiyalarni ushlaydigan metodlardir. Umumiy tutqichlarga quyidagilar kiradi:
get(target, property, receiver): Xususiyatga kirishni ushlaydi.set(target, property, value, receiver): Xususiyatni belgilashni ushlaydi.has(target, property): `in` operatorini ushlaydi.deleteProperty(target, property): `delete` operatorini ushlaydi.apply(target, thisArg, argumentsList): Funktsiya chaqiruvlarini ushlaydi.construct(target, argumentsList, newTarget): `new` operatorini ushlaydi.
Proxy instansiyasida operatsiya bajarilganda, agar tegishli tutqich handlerda aniqlangan bo'lsa, bu tutqich ijro etiladi. Aks holda, operatsiya asl target obyektida davom etadi.
Ichki Obyektlarning Muammosi
Murakkab ilova uchun konfiguratsiya obyekti yoki bir necha darajadagi ruxsatlarga ega foydalanuvchi profilini ifodalovchi ierarxik ma'lumotlar tuzilmasi kabi chuqur ichki obyektlarni o'z ichiga olgan stsenariyni ko'rib chiqing. Agar siz ushbu ichki joylashuvning har qanday darajasidagi xususiyatlarga izchil mantiqni – masalan, validatsiya, loglash yoki kirish nazoratini qo'llashingiz kerak bo'lsa, bitta, tekis proksiydan foydalanish samarasiz va noqulay bo'ladi.
Masalan, foydalanuvchi konfiguratsiya obyekti:
const userConfig = {
id: 123,
profile: {
name: 'Alice',
address: {
street: '123 Main St',
city: 'Anytown',
zip: '12345'
}
},
settings: {
theme: 'dark',
notifications: {
email: true,
sms: false
}
}
};
Agar siz har bir xususiyatga kirishni qayd etishni yoki barcha qator qiymatlari bo'sh bo'lmasligini ta'minlashni istasangiz, odatda obyektni qo'lda aylanib chiqishingiz va proksilarni rekursiv ravishda qo'llashingiz kerak bo'ladi. Bu takrorlanuvchi kodga va ishlash xarajatlariga olib kelishi mumkin.
Proxy Handler Zanjirlarini Tanishish
Proxy handler zanjiri tushunchasi, proksining tutqichi nishonni bevosita manipulyatsiya qilish yoki qiymat qaytarish o'rniga, boshqa proksiyni yaratib, uni qaytarganda yuzaga keladi. Bu zanjirni hosil qiladi, bunda proksidagi operatsiyalar ichki proksilardagi keyingi operatsiyalarga olib kelishi mumkin, bu esa nishon obyektining ierarxiyasini aks ettiruvchi ichki proksi tuzilmasini samarali yaratadi.
Asosiy g'oya shundaki, proksida get tutqichi chaqirilganda va kirilayotgan xususiyatning o'zi obyekt bo'lsa, get tutqichi obyektning o'zini emas, balki shu ichki obyekt uchun yangi Proxy instansiyasini qaytarishi mumkin.
Oddiy Misol: Ko'p darajali Kirishni Qayd Etish
Keling, har bir xususiyatga kirishni, hatto ichki obyektlar ichidagi kirishni ham qayd etuvchi proksiyni yaratamiz.
function createLoggingProxy(obj, path = []) {
return new Proxy(obj, {
get(target, property, receiver) {
const currentPath = [...path, property].join('.');
console.log(`Accessing: ${currentPath}`);
const value = Reflect.get(target, property, receiver);
// If the value is an object and not null, and not a function (to avoid proxying functions themselves unless intended)
if (typeof value === 'object' && value !== null && !Array.isArray(value) && typeof value !== 'function') {
return createLoggingProxy(value, [...path, property]);
}
return value;
},
set(target, property, value, receiver) {
const currentPath = [...path, property].join('.');
console.log(`Setting: ${currentPath} to ${value}`);
return Reflect.set(target, property, value, receiver);
}
});
}
const userConfig = {
id: 123,
profile: {
name: 'Alice',
address: {
street: '123 Main St',
city: 'Anytown',
zip: '12345'
}
}
};
const proxiedUserConfig = createLoggingProxy(userConfig);
console.log(proxiedUserConfig.profile.name);
// Output:
// Accessing: profile
// Accessing: profile.name
// Alice
proxiedUserConfig.profile.address.city = 'Metropolis';
// Output:
// Accessing: profile
// Setting: profile.address.city to Metropolis
Ushbu misolda:
createLoggingProxy– berilgan obyekt uchun proksi yaratadigan fabrika funktsiyasi.- The
gettutqichi kirish yo'lini qayd etadi. - Eng muhimi, agar olingan
valueobyekt bo'lsa, u ichki obyekt uchun yangi proksi qaytarish uchuncreateLoggingProxyni rekursiv ravishda chaqiradi. Zanjir shunday hosil bo'ladi. - The
settutqichi ham o'zgarishlarni qayd etadi.
When proxiedUserConfig.profile.namega kirish amalga oshirilganda, 'profile' uchun birinchi get tutqichi ishga tushadi. Since userConfig.profile obyekt bo'lganligi sababli, createLoggingProxy yana chaqiriladi va profile obyekti uchun yangi proksi qaytaradi. Keyin, bu *yangi* proksida 'name' uchun get tutqichi ishga tushadi. Yo'l ushbu ichki proksilar orqali to'g'ri kuzatiladi.
Ko'p darajali tutish uchun Handler Zanjirining Afzalliklari
Proksi handlerlarini zanjirlash muhim afzalliklarni taqdim etadi:
- Yagona Mantiqni Qo'llash: Takrorlanuvchi kodsiz ichki obyektlarning barcha darajalarida izchil mantiqni (validatsiya, transformatsiya, loglash, kirish nazorati) qo'llang.
- Qisqartirilgan Boilerplate: Har bir ichki obyekt uchun qo'lda aylanib chiqish va proksi yaratishdan saqlaning. Zanjirning rekursiv tabiati buni avtomatik ravishda boshqaradi.
- Yaxshilangan Xizmat Ko'rsatish: Tutish mantig'ingizni bir joyga markazlashtiring, yangilash va o'zgartirishlarni ancha osonlashtiring.
- Dinamik Xatti-harakat: Ichki proksilar orqali harakatlanishda xatti-harakatlarni bir zumda o'zgartirish mumkin bo'lgan yuqori dinamik ma'lumotlar tuzilmalarini yarating.
Ilg'or Foydalanish Holatlari va Nafmunalari
Handler zanjirlash namunasi oddiy loglash bilan cheklanmaydi. Uni murakkab funktsiyalarni amalga oshirish uchun kengaytirish mumkin.
1. Ko'p darajali Ma'lumotlarni Tasdiqlash
Ba'zi maydonlar shartli ravishda talab qilinadigan yoki ma'lum format cheklovlariga ega bo'lgan murakkab forma obyekti bo'ylab foydalanuvchi kiritishini validatsiya qilishni tasavvur qiling.
function createValidatingProxy(obj, path = [], validationRules = {}) {
return new Proxy(obj, {
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
if (typeof value === 'object' && value !== null && !Array.isArray(value) && typeof value !== 'function') {
return createValidatingProxy(value, [...path, property], validationRules);
}
return value;
},
set(target, property, value, receiver) {
const currentPath = [...path, property].join('.');
const rules = validationRules[currentPath];
if (rules) {
if (rules.required && (value === null || value === undefined || value === '')) {
throw new Error(`Validation Error: ${currentPath} is required.`);
}
if (rules.type && typeof value !== rules.type) {
throw new Error(`Validation Error: ${currentPath} must be of type ${rules.type}.`);
}
if (rules.minLength && typeof value === 'string' && value.length < rules.minLength) {
throw new Error(`Validation Error: ${currentPath} must be at least ${rules.minLength} characters long.`);
}
// Add more validation rules as needed
}
return Reflect.set(target, property, value, receiver);
}
});
}
const userProfileSchema = {
name: { required: true, type: 'string', minLength: 2 },
age: { type: 'number', min: 18 },
contact: {
email: { required: true, type: 'string' },
phone: { type: 'string' }
}
};
const userProfile = {
name: '',
age: 25,
contact: {
email: '',
phone: '123-456-7890'
}
};
const proxiedUserProfile = createValidatingProxy(userProfile, [], userProfileSchema);
try {
proxiedUserProfile.name = 'Bo'; // Valid
proxiedUserProfile.contact.email = 'bo@example.com'; // Valid
console.log('Initial profile setup successful.');
} catch (error) {
console.error(error.message);
}
try {
proxiedUserProfile.name = 'B'; // Invalid - minLength
} catch (error) {
console.error(error.message);
}
try {
proxiedUserProfile.contact.email = ''; // Invalid - required
} catch (error) {
console.error(error.message);
}
try {
proxiedUserProfile.age = 'twenty'; // Invalid - type
} catch (error) {
console.error(error.message);
}
Bu yerda createValidatingProxy funksiyasi ichki obyektlar uchun rekursiv ravishda proksilarni yaratadi. The set tutqichi tayinlashga ruxsat berishdan oldin to'liq malakali xususiyat yo'li (masalan, 'profile.name') bilan bog'liq validatsiya qoidalarini tekshiradi.
2. Nozik Kirish Nazorati
Ma'lum xususiyatlarga o'qish yoki yozish ruxsatini cheklash uchun xavfsizlik siyosatlarini amalga oshiring, bu foydalanuvchi rollari yoki kontekstiga asoslanishi mumkin.
function createAccessControlledProxy(obj, accessConfig, path = []) {
// Default access: allow everything if not specified
const defaultAccess = { read: true, write: true };
return new Proxy(obj, {
get(target, property, receiver) {
const currentPath = [...path, property].join('.');
const config = accessConfig[currentPath] || defaultAccess;
if (!config.read) {
throw new Error(`Access Denied: Cannot read property '${currentPath}'.`);
}
const value = Reflect.get(target, property, receiver);
if (typeof value === 'object' && value !== null && !Array.isArray(value) && typeof value !== 'function') {
// Pass down the access config for nested properties
return createAccessControlledProxy(value, accessConfig, [...path, property]);
}
return value;
},
set(target, property, value, receiver) {
const currentPath = [...path, property].join('.');
const config = accessConfig[currentPath] || defaultAccess;
if (!config.write) {
throw new Error(`Access Denied: Cannot write to property '${currentPath}'.`);
}
return Reflect.set(target, property, value, receiver);
}
});
}
const sensitiveData = {
id: 'user-123',
personal: {
name: 'Alice',
ssn: '123-456-7890'
},
preferences: {
theme: 'dark',
language: 'en-US'
}
};
// Define access rules: Admin can read/write everything. User can only read preferences.
const accessRules = {
'personal.ssn': { read: false, write: false }, // Only admins can see SSN
'preferences': { read: true, write: true } // Users can manage preferences
};
// Simulate a user with limited access
const userAccessConfig = {
'personal.name': { read: true, write: true },
'personal.ssn': { read: false, write: false },
'preferences.theme': { read: true, write: true },
'preferences.language': { read: true, write: true }
// ... other preferences are implicitly readable/writable by defaultAccess
};
const proxiedSensitiveData = createAccessControlledProxy(sensitiveData, userAccessConfig);
console.log(proxiedSensitiveData.id); // Accessing 'id' - falls back to defaultAccess
console.log(proxiedSensitiveData.personal.name); // Accessing 'personal.name' - allowed
try {
console.log(proxiedSensitiveData.personal.ssn); // Attempt to read SSN
} catch (error) {
console.error(error.message);
// Output: Access Denied: Cannot read property 'personal.ssn'.
}
try {
proxiedSensitiveData.preferences.theme = 'light'; // Modifying preferences - allowed
console.log(`Theme changed to: ${proxiedSensitiveData.preferences.theme}`);
} catch (error) {
console.error(error.message);
}
try {
proxiedSensitiveData.personal.name = 'Alicia'; // Modifying name - allowed
console.log(`Name changed to: ${proxiedSensitiveData.personal.name}`);
} catch (error) {
console.error(error.message);
}
try {
proxiedSensitiveData.personal.ssn = '987-654-3210'; // Attempt to write SSN
} catch (error) {
console.error(error.message);
// Output: Access Denied: Cannot write to property 'personal.ssn'.
}
Ushbu misol, ma'lum xususiyatlar yoki ichki obyektlar uchun kirish qoidalarini qanday belgilash mumkinligini ko'rsatadi. The createAccessControlledProxy funksiyasi proksi zanjirining har bir darajasida o'qish va yozish operatsiyalari ushbu qoidalarga muvofiqligini ta'minlaydi.
3. Reaktiv Ma'lumotlarni Bog'lash va Holatni Boshqarish
Proxy handler zanjirlari reaktiv tizimlarni qurish uchun asosiy hisoblanadi. Xususiyat o'rnatilganda, siz UI yoki ilovaning boshqa qismlarida yangilanishlarni ishga tushirishingiz mumkin. Bu ko'plab zamonaviy JavaScript freymvorklari va holatni boshqarish kutubxonalarida asosiy konsepsiyadir.
Soddalashtirilgan reaktiv do'konni ko'rib chiqing:
function createReactiveStore(initialState) {
const listeners = new Map(); // Map of property paths to arrays of callback functions
function subscribe(path, callback) {
if (!listeners.has(path)) {
listeners.set(path, []);
}
listeners.get(path).push(callback);
}
function notify(path, newValue) {
if (listeners.has(path)) {
listeners.get(path).forEach(callback => callback(newValue));
}
}
function createProxy(obj, currentPath = '') {
return new Proxy(obj, {
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
const fullPath = currentPath ? `${currentPath}.${String(property)}` : String(property);
if (typeof value === 'object' && value !== null && !Array.isArray(value) && typeof value !== 'function') {
// Recursively create proxy for nested objects
return createProxy(value, fullPath);
}
return value;
},
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver);
const fullPath = currentPath ? `${currentPath}.${String(property)}` : String(property);
// Notify listeners if the value has changed
if (oldValue !== value) {
notify(fullPath, value);
// Also notify for parent paths if the change is significant, e.g., an object modification
if (currentPath) {
notify(currentPath, receiver); // Notify parent path with the whole updated object
}
}
return result;
}
});
}
const proxyStore = createProxy(initialState);
return { store: proxyStore, subscribe, notify };
}
const appState = {
user: {
name: 'Guest',
isLoggedIn: false
},
settings: {
theme: 'light',
language: 'en'
}
};
const { store, subscribe } = createReactiveStore(appState);
// Subscribe to changes
subscribe('user.name', (newName) => {
console.log(`User name changed to: ${newName}`);
});
subscribe('settings.theme', (newTheme) => {
console.log(`Theme changed to: ${newTheme}`);
});
subscribe('user', (updatedUser) => {
console.log('User object updated:', updatedUser);
});
// Simulate state updates
store.user.name = 'Bob';
// Output:
// User name changed to: Bob
store.settings.theme = 'dark';
// Output:
// Theme changed to: dark
store.user.isLoggedIn = true;
// Output:
// User object updated: { name: 'Bob', isLoggedIn: true }
store.user = { ...store.user, name: 'Alice' }; // Reassigning a nested object property
// Output:
// User name changed to: Alice
// User object updated: { name: 'Alice', isLoggedIn: true }
Ushbu reaktiv do'kon misolida, set tutqichi nafaqat tayinlashni bajaradi, balki qiymat haqiqatan ham o'zgarganligini ham tekshiradi. Agar o'zgargan bo'lsa, u ma'lum xususiyat yo'li uchun obuna bo'lgan tinglovchilarga bildirishnomalarni ishga tushiradi. Ichki yo'llarga obuna bo'lish va ular o'zgarganda yangilanishlarni qabul qilish imkoniyati handler zanjirining bevosita foydasidir.
E'tiborga olish kerak bo'lgan jihatlar va Eng Yaxshi Amaliyotlar
Kuchli bo'lishiga qaramay, proksi handler zanjirlarini ishlatish diqqat bilan ko'rib chiqishni talab qiladi:
- Ishlash Qobiliyatining Ortiqcha Yuklanishi: Har bir proksi yaratilishi va tutqichni chaqirish kichik bir yukni qo'shadi. Juda chuqur ichki joylashish yoki juda tez-tez bajariladigan operatsiyalar uchun o'z implementatsiyangizni sinab ko'ring. Biroq, odatiy holatlar uchun afzalliklar ko'pincha kichik ishlash xarajatlaridan ustun turadi.
- Nosozliklarni Tuzatish Murakkabligi: Proksi qilingan obyektlardagi nosozliklarni tuzatish ancha qiyin bo'lishi mumkin. Brauzer ishlab chiquvchisi vositalaridan va loglashdan keng foydalaning. The
receiverargumenti tutqichlardagi to'g'ri `this` kontekstini saqlash uchun juda muhimdir. - `Reflect` API: To'g'ri ishlashni ta'minlash va proksi hamda uning nishoni o'rtasidagi invariant munosabatni saqlash uchun, ayniqsa getterlar, setterlar va prototiplar bilan ishlashda, har doim o'z tutqichlaringiz ichida
ReflectAPI (masalan,Reflect.get,Reflect.set) dan foydalaning. - Aylanma Havolalar: Nishon obyektlaringizdagi aylanma havolalarga e'tibor bering. Agar sizning proksi mantig'ingiz tsikllarni tekshirmasdan ko'r-ko'rona rekursiv ravishda ishlasa, siz cheksiz tsiklga tushib qolishingiz mumkin.
- Massivlar va Funktsiyalar: Massivlar va funktsiyalar bilan qanday ishlashni hal qiling. Yuqoridagi misollar odatda, agar maxsus mo'ljallanmagan bo'lsa, funktsiyalarni bevosita proksi qilishdan saqlanadi va massivlarni, agar aniq dasturlashtirilmagan bo'lsa, ularga rekursiv ravishda kirmasdan boshqaradi. Massivlarni proksi qilish
push,popkabi metodlar uchun maxsus mantiqni talab qilishi mumkin. - O'zgaruvchanlik (Immutability) va O'zgarmaslik (Mutability): Proksi qilingan obyektlaringiz o'zgaruvchan (mutable) yoki o'zgarmas (immutable) bo'lishi kerakligini hal qiling. Yuqoridagi misollar o'zgaruvchan obyektlarni namoyish etadi. O'zgarmas tuzilmalar uchun
settutqichlaringiz odatda xatolarni yuzaga keltiradi yoki tayinlashni e'tiborsiz qoldiradi, vagettutqichlaringiz mavjud qiymatlarni qaytaradi. - `ownKeys` va `getOwnPropertyDescriptor`: Keng qamrovli tutish uchun
ownKeys(`for...in` tsikllari va `Object.keys` uchun) vagetOwnPropertyDescriptorkabi tutqichlarni amalga oshirishni ko'rib chiqing. Ular asl obyektning xatti-harakatini to'liq taqlid qilishi kerak bo'lgan proksilar uchun muhimdir.
Proxy Handler Zanjirlarining Global Ilovalari
Ma'lumotlarni bir necha darajada tutish va boshqarish qobiliyati proksi handler zanjirlarini turli global ilova kontekstlarida bebaho qiladi:
- Xalqaro-lashtirish (i18n) va Lokalizatsiya (l10n): Xalqaro-lashtirilgan ilova uchun murakkab konfiguratsiya obyektini tasavvur qiling. Siz proksilardan foydalanib, foydalanuvchi tiliga (locale) asoslangan tarjimalarni dinamik ravishda olishingiz mumkin, bu ilova UI va backendining barcha darajalarida izchillikni ta'minlaydi. Masalan, UI elementlari uchun ichki konfiguratsiya proksilar tomonidan tutiladigan tilga xos matn qiymatlariga ega bo'lishi mumkin.
- Global Konfiguratsiyani Boshqarish: Keng ko'lamli taqsimlangan tizimlarda konfiguratsiya yuqori darajada ierarxik va dinamik bo'lishi mumkin. Proksilar ushbu ichki konfiguratsiyalarni boshqarishi, qoidalarni tatbiq etishi, turli mikroservislar bo'ylab kirishni qayd etishi va xizmat global miqyosda qayerda joylashganligidan qat'i nazar, atrof-muhit omillari yoki ilova holatiga asoslanib, to'g'ri konfiguratsiya qo'llanilishini ta'minlashi mumkin.
- Ma'lumotlarni Sinxronizatsiya Qilish va Konfliktlarni Hal Qilish: Ma'lumotlar bir nechta mijozlar yoki serverlar o'rtasida sinxronlashtiriladigan taqsimlangan ilovalarda (masalan, real vaqt rejimida hamkorlikda tahrirlash vositalari), proksilar umumiy ma'lumotlar tuzilmalariga yangilanishlarni tutib olishi mumkin. Ular sinxronizatsiya mantiqini boshqarish, konfliktlarni aniqlash va barcha ishtirok etuvchi ob'ektlar bo'ylab, ularning geografik joylashuvi yoki tarmoq kechikishidan qat'i nazar, hal qilish strategiyalarini izchil qo'llash uchun ishlatilishi mumkin.
- Turli Mintaqalardagi Xavfsizlik va Muvo-fiqlik: Nozik ma'lumotlar bilan ishlaydigan va turli global qoidalarga (masalan, GDPR, CCPA) rioya qiluvchi ilovalar uchun proksi zanjirlari aniq kirish nazoratini va ma'lumotlarni maskalash siyosatlarini ta'minlashi mumkin. Proksi ichki obyektda shaxsiy aniqlash mumkin bo'lgan ma'lumotlarga (PII) kirishni tutib olishi va foydalanuvchining mintaqasi yoki e'lon qilingan roziligiga asoslanib, tegishli anonimizatsiya yoki kirish cheklovlarini qo'llashi mumkin, bu esa turli huquqiy doiralar bo'ylab muvo-fiqlikni ta'minlaydi.
Xulosa
JavaScript Proxy handler zanjiri dasturchilarga obyekt operatsiyalari ustidan, ayniqsa murakkab, ichki ma'lumotlar tuzilmalarida nozik nazoratni amalga oshirish imkonini beruvchi murakkab namunadir. Tutqich implementatsiyalari ichida proksilarni rekursiv ravishda yaratishni tushunish orqali siz yuqori dinamik, xizmat ko'rsatish mumkin bo'lgan va mustahkam ilovalar qurishingiz mumkin. Ilg'or validatsiyani, ishonchli kirish nazoratini, reaktiv holatni boshqarishni yoki murakkab ma'lumotlarni manipulyatsiyasini amalga oshirasizmi, proksi handler zanjiri global miqyosdagi zamonaviy JavaScript rivojlanishining murakkabliklarini boshqarish uchun kuchli yechimni taklif etadi.
JavaScript meta-dasturlash sohasidagi sayohatingizni davom ettirar ekansiz, Proxylar va ularning zanjirlash imkoniyatlarining chuqurliklarini o'rganish, shubhasiz, kodingizda yangi darajadagi nafislik va samaradorlikni ochib beradi. Tutish kuchini qabul qiling va butun dunyo auditoriyasi uchun yanada aqlli, tezkor va xavfsiz ilovalar yarating.