Mundarija (37)
- Bu darsda
- 1. Nega bu kerak?
- 2. Eslaymiz: har bir belgi — son
- 3. JavaScript satri ichida nima bor?
- 3.1 Kod birligi — 16 bitli katak
- 3.2 length kataklarni sanaydi
- 3.3 Surrogat juftlik: emoji'ning ikki yarmi
- 4. Kod nuqtalari bilan ishlash
- 4.1 codePointAt — belgining kodini olish
- 4.2 String.fromCodePoint — koddan belgi yasash
- 4.3 [...matn] — kod nuqtalari bo'yicha bo'lish
- 5. Grafema klasteri: ko'z ko'rgan "bitta belgi"
- 5.1 Bir nechta kod nuqtasidan yig'ilgan emoji
- 5.2 Grafemalarni sanash: Intl.Segmenter
- 6. Normalizatsiya: bir xil ko'rinadi, lekin teng emas
- 6.1 Muammo: bir harf, ikki xil yozuv
- 6.2 normalize() — bitta shaklga keltirish
- 6.3 Qoida: solishtirishdan oldin normalizatsiya
- 6.4 NFKC: "o'xshash" belgilarni ham birlashtirish
- 6.5 O'zbekcha o' va g': bu yerda normalize yordam bermaydi
- 7. Buzilgan matn: isWellFormed va toWellFormed
- 7.1 Yolg'iz yarim qayerdan paydo bo'ladi?
- 7.2 toWellFormed() — tuzatish
- 7.3 Eng yaxshisi — umuman buzmaslik
- 8. Ko'p uchraydigan xatolar
- 8.1 Cheklovni length bilan tekshirish
- 8.2 Emoji'li matnni indeks yoki split("") bilan bo'lish
- 8.3 Normalizatsiyasiz solishtirish
- 8.4 Kirillcha va lotincha "bir xil" harflar
- 8.5 Mavjud bo'lmagan kod nuqtasi
- 9. Mashqlar
- 1-mashq (oson): Uch xil sanash
- 2-mashq (o'rta): Belgi kodlari
- 3-mashq (qiyin): Qidiruvni ishonchli qilish
- 10. Real ishda
- Xulosa
- Manbalar
JavaScript'da Unicode va emoji: matn ichki tuzilishi
Qisqacha: JavaScript satrni UTF-16 kod birliklarida saqlaydi. Oddiy harf bitta birlik oladi, emoji esa ikkita, shuning uchun kulib turgan yuz emoji'sining
lengthqiymati2. Belgilarni kod nuqtasi bo'yicha sanash uchun[...matn].length, kodini bilish uchuncodePointAt(0)ishlatiladi. Ko'rinishi bir xil, lekin ichi har xil matnlarninormalize()bilan bir shaklga keltirib, keyin solishtirasiz.
Bu darsda
- Nega emoji'ning
lengthqiymati2ekanini tushuntira olasiz. - Kod nuqtasi va kod birligi farqini bilasiz,
codePointAtvaString.fromCodePointbilan ishlay olasiz. - Ko'z ko'radigan "bitta belgi" ba'zan 5–8 ta kod birligidan iborat bo'lishini taniysiz.
normalize()bilan kirillcha va lotincha matnlarni to'g'ri solishtira olasiz.- Buzilgan matnni
isWellFormed()bilan aniqlab,toWellFormed()bilan tuzata olasiz.
Oldin bilishingiz kerak: Matn kodlash: ASCII, Unicode va UTF-8, String asoslari, Kesish, bo'lish va to'ldirish, Matnni o'zgartirish va tozalash.
1. Nega bu kerak?
Tasavvur qiling: siz Telegram bot uchun ro'yxatdan o'tish kodini yozyapsiz. Qoida: ism 8 belgidan oshmasin. Malika ismini yozib, oxiriga kulib turgan yuz emoji'sini qo'ydi.
Ekranda 8 ta belgi ko'rinadi: 6 ta harf, bo'sh joy va emoji. JavaScript esa boshqacha sanaydi:
const ism = "Malika \u{1F600}";
console.log(ism.length); // 99! Bot Malikani rad etadi, garchi u qoidani buzmagan bo'lsa ham.
Undan ham yomoni bor. Ismni 8 belgigacha "kesib" qo'ysangiz, emoji o'rtasidan bo'linadi:
const ism = "Malika \u{1F600}";
console.log(ism.slice(0, 8)); // Malika �Oxiridagi � — "bu yerda buzilgan belgi bor" degan belgi. Foydalanuvchi o'z ismi o'rnida g'alati so'roq belgisini ko'radi.
Keling, JavaScript matnni ichida qanday saqlashini ko'ramiz — shunda length qachon "yolg'on gapirishini" bilasiz.
Maslahat: Bu darsda emoji'larni kod ichida
"\u{1F600}"ko'rinishida yozamiz. Bu String asoslari darsidagi escape:\u{...}ichiga belgining kodi yoziladi."\u{1F600}"va klaviaturadan terilgan kulib turgan yuz — aynan bir xil satr. Kodni o'zingiz ishga tushirsangiz, konsolda emoji'ning o'zi chiqadi.
2. Eslaymiz: har bir belgi — son
Matn kodlash darsida ko'rgan edik: Unicode dunyodagi har bir belgiga raqam beradi. Bu raqam kod nuqtasi (code point) deyiladi va U+0041 shaklida yoziladi. U+ dan keyingi qism — o'n oltilik son.
| Belgi | Kod nuqtasi | O'nlik son |
|---|---|---|
A |
U+0041 | 65 |
a |
U+0061 | 97 |
ў (kirillcha) |
U+045E | 1118 |
| kulib turgan yuz | U+1F600 | 128512 |
O'sha darsda UTF-8 ni ham ko'rgansiz: u belgini 1–4 baytga aylantirib, faylga yozadi. JavaScript esa xotirada matnni boshqa usulda — UTF-16 da saqlaydi. Bu darsning hamma "g'alati" natijalari shundan kelib chiqadi.
3. JavaScript satri ichida nima bor?
3.1 Kod birligi — 16 bitli katak
JavaScript satrini bir qator kataklar deb tasavvur qiling. Har bir katak 16 bitli. Bunday katak kod birligi (code unit) deyiladi.
16 bitga eng ko'pi bilan 65 536 xil son sig'adi (2 ning 16-darajasi — ikkilik sanoq darsidan). Lotin, kirill, arab harflari, raqamlar va tinish belgilarining kodlari shu chegaradan kichik. Shuning uchun ular bitta katakka sig'adi.
Emoji'larning kodlari esa katta: kulib turgan yuz — 128 512. Bu son bitta katakka sig'maydi va JavaScript uni ikkita katakka bo'lib yozadi.
Hayotdan o'xshatish: pochta qutilari. Oddiy xat bitta qutiga sig'adi. Katta posilka esa ikki qutini egallaydi. Pochtachi "nechta quti band?" deb so'rasa, siz qutilarni sanaysiz, posilkalarni emas.
3.2 length kataklarni sanaydi
length — aynan shu "band qutilar" soni. U belgilarni emas, kod birliklarini sanaydi:
console.log("Salom".length); // 5
console.log("Ўзбек".length); // 5
console.log("\u{1F600}".length); // 2
console.log("Salom \u{1F44B}".length); // 8Kirillcha so'z ham oddiy: har bir harf bitta katak. Faqat emoji ikkita katak oldi. Oxirgi misolda qo'l silkitish emoji'si (U+1F44B) bor: 5 harf, 1 bo'sh joy va emoji uchun 2 katak — jami 8.
3.3 Surrogat juftlik: emoji'ning ikki yarmi
Katta belgini tashkil qilgan ikki katak surrogat juftlik (surrogate pair) deyiladi. "Surrogat" — "o'rinbosar": bu ikki son o'zicha hech narsani bildirmaydi, faqat birga turganda bitta belgini beradi.
Juftlikni o'z ko'zingiz bilan ko'rish mumkin. String asoslari darsidagi to'rt xonali \uXXXX escape bilan ikki yarmini alohida yozamiz:
console.log("\u{1F600}" === "\uD83D\uDE00"); // truetrue! Ya'ni kulib turgan yuz aslida ikki kod birligidan iborat: D83D va DE00. \u{1F600} yozuvi — JavaScript'ga "o'zing ikkiga bo'lib yoz" degan qulaylik.
Endi indeks bilan murojaat qilsak nima bo'lishi tushunarli:
const kulgi = "\u{1F600}";
console.log(kulgi[0]); // �
console.log(kulgi.at(-1)); // �kulgi[0] — faqat birinchi yarmi. Yarim emoji hech qanday belgi emas, shuning uchun konsol o'rniga � chiqaradi. Bunday yakka qolgan yarim yolg'iz surrogat (lone surrogate) deyiladi.
Tekshirib ko'ring:
"Ali\u{1F600}".lengthnechaga teng?
Javob
5. Ali — 3 ta kod birligi, emoji — 2 ta. length ekrandagi belgilarni (4 ta) emas, kataklarni sanaydi.
Tekshirib ko'ring: Nega
"Ўзбек".length—5, emoji'li satrda esalengthko'payib ketadi? Axir kirill harflari ham ASCII'da yo'q-ku.
Javob
Muhimi ASCII emas, 16 bitga sig'ish. Kirill harflarining kodi (masalan, U+045E = 1118) 65 536 dan kichik, shuning uchun har biri bitta kod birligi. Emoji kodi (128 512) katta — unga ikkita kerak.
4. Kod nuqtalari bilan ishlash
4.1 codePointAt — belgining kodini olish
codePointAt(i) — i-o'rindan boshlanadigan belgining kod nuqtasini qaytaradi:
console.log("A".codePointAt(0)); // 65
console.log("ў".codePointAt(0)); // 1118
console.log("\u{1F600}".codePointAt(0)); // 128512Uchinchi qatorga e'tibor bering: codePointAt ikki yarimni birlashtirib, butun emoji kodini berdi.
Kodni U+ shaklida ko'rish uchun o'n oltilikka o'tkazamiz. .toString(16) ni turlarni o'zgartirish darsida qisqa ko'rgan edik, son yozish shakllari darsida batafsil o'rganamiz:
const kod = "\u{1F600}".codePointAt(0);
console.log(kod.toString(16)); // 1f6001f600 — ya'ni U+1F600. Unicode jadvalidagi yozuv bilan bir xil.
Eski kodda charCodeAt(i) ni ham uchratasiz. U kod nuqtasini emas, bitta kod birligini qaytaradi:
const kulgi = "\u{1F600}";
console.log(kulgi.charCodeAt(0)); // 55357
console.log(kulgi.charCodeAt(1)); // 56832
console.log(kulgi.codePointAt(0)); // 128512charCodeAt emoji'ni ikki bo'lak qilib ko'rsatdi — 55357 (D83D) va 56832 (DE00). Oddiy harflarda ikkala metod bir xil natija beradi. Emoji bo'lishi mumkin bo'lgan matnda esa codePointAt ishlating.
Diqqat:
codePointAt(i)dagii— baribir kod birligi indeksi."\u{1F600}".codePointAt(1)emoji'ning ikkinchi yarmini beradi (56832), keyingi belgini emas.
4.2 String.fromCodePoint — koddan belgi yasash
Teskari yo'l ham bor. String.fromCodePoint(kod) — kod nuqtasidan satr yasaydi:
console.log(String.fromCodePoint(65)); // A
console.log(String.fromCodePoint(1118)); // ў
console.log(String.fromCodePoint(79, 107)); // OkBir nechta kod bersangiz, ulardan bitta satr yig'iladi. String. qismi — xuddi tenglik darsidagi Object.is kabi: String "asboblar qutisi", fromCodePoint — undagi asbob.
Kodni o'n oltilik shaklda ham berish mumkin. 0x bilan boshlangan son — o'n oltilik yozuv, uni o'n oltilik sanoq darsida ko'rgansiz:
const pizza = String.fromCodePoint(0x1F355);
console.log(pizza.length); // 2
console.log(pizza === "\u{1F355}"); // trueEski String.fromCharCode esa faqat bitta kod birligini qabul qiladi. Emoji uchun unga ikkala yarimni berish kerak bo'ladi — shuning uchun zamonaviy kodda fromCodePoint ishlatiladi.
4.3 [...matn] — kod nuqtalari bo'yicha bo'lish
Endi asosiy savol: ekrandagi belgilarni qanday sanaymiz? Birinchi qadam — [...matn] yozuvi:
const ism = "Malika \u{1F600}";
console.log(ism.length); // 9
console.log([...ism].length); // 8[...ism] satrni kod nuqtalari bo'yicha bo'laklarga ajratadi va ro'yxat yasaydi. Emoji endi ikki yarim emas, bitta butun bo'lak. Natija — 8, Malikaning ismi qoidaga mos.
Bu yozuvda ikki yangi narsa bor, ikkalasini keyinroq to'liq o'rganamiz:
...— spread operatori. U satrni "sochib", bo'laklarini birma-bir chiqaradi. To'liq — spread va rest darsida.[ ]— massiv (array), ya'ni qiymatlar ro'yxati. Massivlar — massiv darsida.
Hozircha [...matn].length ni tayyor formula sifatida eslab qoling: "kod nuqtalari soni".
Kesish darsidagi split("") bunday qilmaydi — u kod birliklari bo'yicha bo'ladi:
const salom = "Salom \u{1F44B}";
console.log(salom.split("").length); // 8
console.log([...salom].length); // 7split("") emoji'ni ikki buzuq yarimga ajratdi. Emoji bo'lishi mumkin bo'lgan matnni hech qachon split("") bilan bo'lmang.
Maslahat: Satr bo'ylab belgima-belgi yuradigan
for...ofsikli ham kod nuqtalari bo'yicha ishlaydi — emoji'ni buzmaydi. Unifor...ofvafor...indarsida ko'ramiz.
Endi o'zingiz to'ldirib ko'ring. Satrda "yurak" emoji'si (U+2764) va qo'l silkitish (U+1F44B) bor:
const xabar = "Rahmat \u2764 \u{1F44B}";
console.log(xabar.length); // 11
console.log([...xabar].length); // 10- "Rahmat" va ikki bo'sh joy — 8 ta kod birligi.
- Yurak (U+2764) kodi 65 536 dan kichik, u egallaydi: ta kod birligi.
- Qo'l silkitish (U+1F44B) katta, u egallaydi: ta kod birligi.
- Shuning uchun
length— 11,[...xabar].lengthesa .
Tekshirib ko'ring:
[..."Ok\u{1F600}"].lengthva"Ok\u{1F600}".lengthnimaga teng?
Javob
3 va 4. Spread kod nuqtalarini sanaydi: O, k va emoji — 3 ta. length esa kod birliklarini: 2 + 2 = 4.
5. Grafema klasteri: ko'z ko'rgan "bitta belgi"
5.1 Bir nechta kod nuqtasidan yig'ilgan emoji
[...matn] ham har doim ko'zga ko'ringan belgilar sonini bermaydi. Ba'zi emoji'lar bir nechta kod nuqtasidan yig'iladi.
"Oila" emoji'si (ota, ona va qiz) aslida uchta alohida emoji. Ular orasiga ko'rinmas "yelim" belgi qo'yilgan — ZWJ (Zero Width Joiner), U+200D. Telefon bu uchlikni ko'rib, bitta rasm chizadi:
const oila = "\u{1F468}\u200D\u{1F469}\u200D\u{1F467}";
console.log(oila.length); // 8
console.log([...oila].length); // 5Ekranda — bitta rasm. length — 8. [...oila] — 5 (uchta odam va ikkita "yelim").
Boshqa misollar ham bor:
| Nima ko'rinadi | Nimadan yig'ilgan | length |
[...s].length |
|---|---|---|---|
| O'zbekiston bayrog'i | ikki "harf-belgi" U+1F1FA va U+1F1FF | 4 | 2 |
| teri rangli bosh barmoq | barmoq + rang belgisi | 4 | 2 |
| oila | 3 odam + 2 ta ZWJ | 8 | 5 |
Ko'z bitta belgi deb qabul qiladigan bunday guruh grafema klasteri (grapheme cluster) deyiladi. Bu atamani Matn kodlash darsida eshitgansiz.
5.2 Grafemalarni sanash: Intl.Segmenter
Ekrandagi belgilarni aniq sanash uchun brauzer va Node'da tayyor vosita bor — Intl.Segmenter. U matnni grafema klasterlariga bo'ladi:
const oila = "\u{1F468}\u200D\u{1F469}\u200D\u{1F467}";
const bayroq = "\u{1F1FA}\u{1F1FF}";
const bolgich = new Intl.Segmenter("uz");
console.log([...bolgich.segment(oila)].length); // 1
console.log([...bolgich.segment(bayroq)].length); // 1Nihoyat 1 — ko'z ko'rgani bilan bir xil. Bu kodda hali o'tilmagan narsalar ko'p: new so'zi, segment metodi. Hozircha buni tayyor retsept deb qabul qiling: "ekrandagi belgilar soni". Intl.Segmenter ni Intl.Collator va Intl.Segmenter darsida to'liq o'rganamiz.
Uch xil sanashni bir joyga yig'amiz:
| Usul | Nimani sanaydi | Oila emoji'si |
|---|---|---|
s.length |
kod birliklarini (16 bitli kataklar) | 8 |
[...s].length |
kod nuqtalarini (Unicode belgilari) | 5 |
Intl.Segmenter |
grafema klasterlarini (ko'z ko'rgani) | 1 |
Tekshirib ko'ring: Foydalanuvchiga "sharhingiz 200 belgidan oshmasin" deysiz. Qaysi usul bilan sanash adolatli?
Javob
Intl.Segmenter — u foydalanuvchi ekranda ko'rgan belgilarni sanaydi. length bilan sanasangiz, emoji ishlatgan odam "jarimaga" tushadi: bitta bayroq uchun unga 4 belgi yoziladi.
6. Normalizatsiya: bir xil ko'rinadi, lekin teng emas
6.1 Muammo: bir harf, ikki xil yozuv
Kirillcha Ў harfini kompyuterda ikki xil yozish mumkin:
- Tayyor harf — bitta kod nuqtasi: U+040E.
- Harf + belgi — oddiy
У(U+0423) va uning ustiga qo'yiladigan yoy belgisi (U+0306).
Ekranda ikkalasi bir xil ko'rinadi. JavaScript uchun esa ular har xil satr:
const tayyor = "\u040E";
const yigilgan = "У\u0306";
console.log(tayyor, yigilgan); // Ў Ў
console.log(tayyor === yigilgan); // false
console.log(tayyor.length, yigilgan.length); // 1 2Ustiga qo'yiladigan bunday belgi birikuvchi belgi (combining mark) deyiladi. U o'zicha ko'rinmaydi — oldingi harfga "yopishadi".
Hayotdan o'xshatish: bir choyxonada shakarli choyni tayyor olib kelishadi, boshqasida choy va shakarni alohida berib, o'zingiz aralashtirasiz. Piyolada ikkalasi bir xil, lekin stolga bitta narsa yoki ikkita narsa kelgan.
Bunday matn macOS'dagi fayl nomlaridan, PDF yoki Word'dan nusxa olinganda keladi. Natijada qidiruv Ўзбекистон so'zini topolmaydi, garchi u ekranda turgan bo'lsa ham.
6.2 normalize() — bitta shaklga keltirish
Yechim — normalizatsiya (normalization): matnni kelishilgan bitta shaklga keltirish. Unicode ikki asosiy shakl belgilagan:
| Shakl | Ma'nosi | Ў qanday bo'ladi |
|---|---|---|
| NFC | yig'ish: harf va belgini bitta tayyor harfga birlashtiradi | 1 kod nuqtasi |
| NFD | yoyish: tayyor harfni asos harf va belgiga ajratadi | 2 kod nuqtasi |
JavaScript'da bu normalize() metodi bilan qilinadi:
const tayyor = "\u040E";
const yigilgan = "У\u0306";
console.log(yigilgan.normalize("NFC") === tayyor); // true
console.log(tayyor.normalize("NFD") === yigilgan); // true
console.log(yigilgan.normalize().length); // 1Qavs bo'sh bo'lsa, normalize() NFC ni tanlaydi. Veb va ma'lumotlar bazalarida ham asosan NFC ishlatiladi.
Matnni o'zgartirish darsidagi boshqa metodlar kabi, normalize() ham asl satrni o'zgartirmaydi — yangi satr qaytaradi.
6.3 Qoida: solishtirishdan oldin normalizatsiya
Matnni solishtirish, qidirish yoki bazaga saqlashdan oldin ikkala tomonni bir shaklga keltiring:
const hujjat = "Мен Ўзбекистонда яшайман".normalize("NFD");
const qidiruv = "Ўзбекистон";
console.log(hujjat.includes(qidiruv)); // false
console.log(hujjat.normalize().includes(qidiruv)); // truehujjat ni ataylab NFD ga o'tkazdik — go'yo matn macOS'dan kelgandek. Birinchi qidiruv hech narsa topmadi. Normalizatsiyadan keyin esa includes so'zni topdi.
Kirillchada NFD ga ajraladigan harflar kam: ў, й, ё. қ, ғ, ҳ esa ajralmaydi. Lotin yozuvida é, ü, ş kabi harflar ajraladi.
6.4 NFKC: "o'xshash" belgilarni ham birlashtirish
Yana ikki shakl bor — NFKC va NFKD. Ular faqat ko'rinishi farq qiladigan belgilarni ham oddiy shaklga keltiradi:
console.log("\u2460".normalize("NFKC")); // 1
console.log("\u00B2".normalize("NFKC")); // 2
console.log("\uFF33alom".normalize("NFKC")); // SalomDoira ichidagi bir (①), daraja belgisi (²), keng "S" harfi — hammasi oddiy belgiga aylandi. NFKC foydalanuvchi nomi va qidiruvda foydali: "Salom" va "Salom" bir xil topiladi. Lekin oddiy matnni saqlashda uni ishlatmang — x² ni x2 ga aylantirib, ma'noni buzadi.
6.5 O'zbekcha o' va g': bu yerda normalize yordam bermaydi
Matn kodlash darsidan eslang: o'zbekcha o' harfidagi belgi to'rt xil yozilishi mumkin. Kodlari bilan ko'ramiz:
const variantlar = "' \u02BB \u2018 \u2019";
console.log(variantlar); // ' ʻ ‘ ’
console.log("o\u02BB".codePointAt(1)); // 699
console.log("o\u2018".codePointAt(1)); // 8216| Belgi | Kodi | Nima |
|---|---|---|
' |
U+0027 | oddiy apostrof (klaviaturada bor) |
ʻ |
U+02BB | rasmiy o'zbek belgisi |
‘ |
U+2018 | ochuvchi qo'shtirnoq |
’ |
U+2019 | yopuvchi qo'shtirnoq |
Bular turli belgilar, bitta belgining ikki yozuvi emas. Shuning uchun normalize() — hatto NFKC ham — ularni birlashtirmaydi:
const rasmiy = "o\u02BBzbek";
console.log(rasmiy.normalize("NFKC") === "o'zbek"); // falseBu yerda replaceAll yordam beradi. Har bir variantni oddiy apostrofga almashtiramiz:
const matn = "o\u02BBzbek tili, g\u2018alaba, o\u2019qish";
const toza = matn
.replaceAll("\u02BB", "'")
.replaceAll("\u2018", "'")
.replaceAll("\u2019", "'");
console.log(toza); // o'zbek tili, g'alaba, o'qishEndi toza ichida qidirish va solishtirish ishonchli.
Tekshirib ko'ring:
"У\u0306" === "Ў"natijasifalse. Qaysi bitta metod chaqiruvi bilan unitruega aylantirasiz?
Javob
"У\u0306".normalize() === "Ў" — true. normalize() (NFC) У va yoy belgisini bitta tayyor Ў ga yig'adi. Xavfsizroq usul — ikkala tomonni ham normalizatsiya qilish.
Tekshirib ko'ring: Nega
"o\u02BB".normalize("NFKC")natijasi"o'"ga teng emas?
Javob
Chunki U+02BB va oddiy apostrof U+0027 — Unicode'da ikki mustaqil belgi. Normalizatsiya faqat bitta belgining turli yozuvlarini birlashtiradi. Bunday belgilarni replaceAll bilan o'zingiz almashtirasiz.
7. Buzilgan matn: isWellFormed va toWellFormed
7.1 Yolg'iz yarim qayerdan paydo bo'ladi?
Darsning boshida ko'rdik: slice emoji'ni o'rtasidan kesib, yolg'iz surrogat qoldiradi. Bunday satr buzilgan hisoblanadi. Uni tekshirish uchun isWellFormed() metodi bor ("to'g'ri tuzilganmi?"):
const ism = "Malika \u{1F600}";
const kesilgan = ism.slice(0, 8);
console.log(ism.isWellFormed()); // true
console.log(kesilgan.isWellFormed()); // falseBuzilgan satr ba'zi joylarda jiddiy xato beradi. Masalan, encodeURIComponent — matnni URL (sayt manzili) ichiga xavfsiz qo'yish uchun tayyor funksiya. U buzilgan satrni umuman qabul qilmaydi:
const kesilgan = "Malika \u{1F600}".slice(0, 8);
console.log(encodeURIComponent(kesilgan));URIError: URI malformedTarjimasi: "URI noto'g'ri tuzilgan". URI — sayt manzilining umumiy nomi. Yolg'iz surrogatni hech qaysi belgiga aylantirib bo'lmaydi, shuning uchun funksiya ishni to'xtatdi. Sayt manzillarini URL anatomiyasi darsida ko'rgansiz.
7.2 toWellFormed() — tuzatish
toWellFormed() har bir yolg'iz yarimni � (U+FFFD, "almashtirish belgisi") bilan almashtiradi:
const kesilgan = "Malika \u{1F600}".slice(0, 8);
const tuzatilgan = kesilgan.toWellFormed();
console.log(tuzatilgan.isWellFormed());
console.log(encodeURIComponent(tuzatilgan));Konsolda:
true
Malika%20%EF%BF%BDEndi xato yo'q. %20 — bo'sh joy, %EF%BF%BD — � belgisining UTF-8 baytlari. Ikkala metod ham yangi: 2023-yildan beri barcha zamonaviy brauzerlarda va Node 20 dan boshlab bor.
7.3 Eng yaxshisi — umuman buzmaslik
toWellFormed() — "shoshilinch yordam". Yaxshisi, matnni kod nuqtalari bo'yicha kesing. Buning uchun [...matn] ro'yxatini kesib, yana satrga yopishtiramiz:
const ism = "Malika \u{1F600} Karimova";
const qisqa = [...ism].slice(0, 8).join("");
console.log(qisqa.isWellFormed()); // true
console.log([...qisqa].length); // 8slice va join bu yerda massiv metodlari: birinchisi ro'yxatning boshini oladi, ikkinchisi bo'laklarni satrga yopishtiradi. Ularni massivni kesish va birlashtirish darsida o'rganamiz. Oila emoji'si kabi grafemalarni ham buzmaslik uchun Intl.Segmenter kerak bo'ladi.
8. Ko'p uchraydigan xatolar
8.1 Cheklovni length bilan tekshirish
"Ism 20 belgidan oshmasin" qoidasini ism.length <= 20 bilan tekshirsangiz, emoji ishlatgan foydalanuvchi adolatsiz rad etiladi. Foydalanuvchi ko'rgan belgilar muhim bo'lsa — [...ism].length yoki Intl.Segmenter. Texnik chegara (masalan, Telegram API) muhim bo'lsa — length.
8.2 Emoji'li matnni indeks yoki split("") bilan bo'lish
matn[i], at(-1), slice va split("") kod birliklari bilan ishlaydi. Emoji'ni o'rtasidan kesib, � qoldiradi. Emoji bo'lishi mumkin bo'lgan matnda [...matn] dan foydalaning.
8.3 Normalizatsiyasiz solishtirish
Ikki satr ekranda bir xil, lekin === false beryapti. Birinchi gumon — normalizatsiya. length ni solishtiring: farq qilsa, ikkala tomonga .normalize() qo'shing.
8.4 Kirillcha va lotincha "bir xil" harflar
const lotin = "a";
const kirill = "\u0430";
console.log(lotin === kirill); // false
console.log(lotin.codePointAt(0), kirill.codePointAt(0)); // 97 1072Kirillcha а (U+0430) va lotincha a ko'rinishda farqsiz, lekin kodlari boshqa. Aralash yozilgan matnda qidiruv ishlamay qoladi. Firibgarlar ham bundan foydalanadi: apple.com o'rniga kirillcha "а" bilan boshlangan soxta manzil ro'yxatdan o'tkazishadi. Shubha bo'lsa — codePointAt bilan tekshiring. Kod 1024–1279 oralig'ida bo'lsa, harf kirillcha.
8.5 Mavjud bo'lmagan kod nuqtasi
console.log(String.fromCodePoint(1114112));RangeError: Invalid code point 1114112Tarjimasi: "Noto'g'ri kod nuqtasi: 1114112". RangeError — "qiymat ruxsat etilgan oraliqdan tashqarida". Unicode'dagi eng katta kod — U+10FFFF, ya'ni 1 114 111. Undan katta yoki manfiy son bersangiz, shu xato chiqadi. Kasr son ham qabul qilinmaydi.
9. Mashqlar
1-mashq (oson): Uch xil sanash
Quyidagi satrlarning har biri uchun length va [...s].length ni konsolga chiqaring. Avval natijani qog'ozda taxmin qiling, keyin tekshiring.
"Toshkent""Toshkent \u{1F3D9}"(shahar emoji'si)"\u{1F1FA}\u{1F1FF}"(O'zbekiston bayrog'i)
Ishora: [...s].length — kod nuqtalari soni.
Yechim
const a = "Toshkent";
const b = "Toshkent \u{1F3D9}";
const c = "\u{1F1FA}\u{1F1FF}";
console.log(a.length, [...a].length); // 8 8
console.log(b.length, [...b].length); // 11 10
console.log(c.length, [...c].length); // 4 2- Faqat harflar bo'lsa, ikkala usul bir xil javob beradi.
- Shahar emoji'si ikki kod birligi, lekin bitta kod nuqtasi — farq 1.
- Bayroq ikki kod nuqtasidan (ikki "harf-belgi") yig'ilgan, har biri ikki kod birligi. Ekranda esa bitta bayroq.
2-mashq (o'rta): Belgi kodlari
"Ўзбек"so'zining birinchi harfi kodinicodePointAtbilan chiqaring.- Shu kodni
String.fromCodePointga berib, harfni qayta yasang. "\u{1F4DA}"(kitoblar emoji'si) kodini o'n oltilik shaklda chiqaring. Ishora:.toString(16).
Yechim
const soz = "Ўзбек";
const kod = soz.codePointAt(0);
console.log(kod); // 1038
console.log(String.fromCodePoint(kod)); // Ў
const kitob = "\u{1F4DA}";
console.log(kitob.codePointAt(0).toString(16)); // 1f4daKatta Ў kodi 1038 (U+040E). Kichik ў — 1118 (U+045E): katta va kichik harf — har xil belgilar. Kitoblar emoji'sining kodi — 1f4da, ya'ni U+1F4DA.
3-mashq (qiyin): Qidiruvni ishonchli qilish
Foydalanuvchi qidiruv maydoniga "o\u2018qituvchi" yozdi (qo'shtirnoqli ‘ bilan). Bazada esa "O'qituvchi Malika Karimova" saqlangan (oddiy apostrof bilan). Kod shunday bo'lsin:
- Ikkala satrni kichik harfga o'tkazing (
toLowerCase— matnni o'zgartirish darsidan). ‘,’vaʻbelgilarni oddiy'ga almashtiring (replaceAll).- Ikkala tomonni
normalize()qiling. - Bazadagi matnda qidiruv so'zi bormi —
includesbilan tekshiring va natijani ternary bilan"Topildi"yoki"Topilmadi"deb chiqaring.
Avval tozalashsiz tekshirib ko'ring — nima chiqadi?
Yechim
const qidiruv = "o\u2018qituvchi";
const baza = "O'qituvchi Malika Karimova";
console.log(baza.includes(qidiruv)); // false
const q = qidiruv
.toLowerCase()
.replaceAll("\u2018", "'")
.replaceAll("\u2019", "'")
.replaceAll("\u02BB", "'")
.normalize();
const b = baza
.toLowerCase()
.replaceAll("\u2018", "'")
.replaceAll("\u2019", "'")
.replaceAll("\u02BB", "'")
.normalize();
console.log(b.includes(q) ? "Topildi" : "Topilmadi"); // TopildiTozalashsiz qidiruv ikki sababga ko'ra ishlamadi: katta-kichik harf farqi (O va o) va apostrof farqi. Ikkala tomonni bir xil qoida bilan tozalagach, qidiruv ishladi.
Bitta kodni ikki marta yozganimizni sezdingizmi? Bu yomon belgi. Funksiyalar darsida bunday tozalashni bitta funksiyaga yig'ishni o'rganamiz — keyin tozala(qidiruv) va tozala(baza) deb yozasiz.
10. Real ishda
- Telegram bot: Bot API'da matnning qaysi bo'lagi qalin yoki havola ekani UTF-16 kod birliklarida o'lchanadi — aynan
lengthkabi. Emoji'li xabarda formatlash "surilib" ketsa, sabab shu. - SMS xizmatlari: lotin harflaridagi SMS 160 belgigacha sig'adi. Kirillcha yoki emoji'li SMS esa 16 bitli kodlashda yuboriladi va faqat 70 belgi sig'adi. SMS narxini hisoblaydigan kod shuni bilishi kerak.
- Formalar: HTML'dagi
maxlengthatributi ham kod birliklarini sanaydi. Server esa boshqacha sanashi mumkin: masalan, PostgreSQL'dalength()kod nuqtalarini sanaydi. Bir xil qoida uchun brauzer va serverda turli natija chiqadi. - Qidiruv va ro'yxatdan o'tish: foydalanuvchi nomlari va qidiruv so'rovlari saqlashdan oldin normalizatsiya qilinadi. Aks holda bir xil ko'rinadigan ikki
Ўктамro'yxatdan o'tadi. - Intervyu: "Nega emoji'ning
lengthqiymati 2?" — frontend intervyularida tez-tez beriladi. Javob: "JavaScript satri UTF-16 kod birliklaridan iborat, emoji esa ikki birlikli surrogat juftlik".
Xulosa
- JavaScript satri — 16 bitli kod birliklari ketma-ketligi.
length,matn[i],sliceshular bilan ishlaydi. - Kodi 65 535 dan katta belgi (emoji) surrogat juftlik — ikki kod birligi bo'lib saqlanadi.
codePointAtvaString.fromCodePointkod nuqtalari bilan ishlaydi.[...matn].lengthkod nuqtalarini sanaydi.- Ekrandagi "bitta belgi" (grafema klasteri) bir nechta kod nuqtasidan iborat bo'lishi mumkin — ularni
Intl.Segmentersanaydi. - Solishtirishdan oldin
normalize()qiling. O'zbekcha apostroflarni esareplaceAllbilan bitta shaklga keltiring. - Kesilgan matnni
isWellFormed()bilan tekshiring,toWellFormed()bilan tuzating.
Manbalar
- MDN: String — UTF-16 characters, Unicode code points, and grapheme clusters
- MDN: String.prototype.normalize()
Keyingi dars: Number turi JS'da: EPSILON, MAX_SAFE_INTEGER, NaN va -0 — matndan sonlarga o'tamiz va JavaScript sonlari ichida qanday chegaralar borligini ko'ramiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!