Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Teg funksiyasi nima oladi
- 2.1 Birinchi teg
- 2.2 Bo'linish qoidasi
- 2.3 Qayta yig'ish: o'z tegingiz
- 2.4 Xuddi shu yig'ish — reduce bilan
- 2.5 Teg satr qaytarishi shart emas
- 3. raw: teskari chiziqni boricha olish
- 3.1 Ikki xil qism: tayyor va xom
- 3.2 String.raw: tayyor teg
- 3.3 Noto'g'ri escape: tegsiz — xato, teg bilan — undefined
- 4. Qismlar massivi haqida ikki nozik fakt
- 4.1 Massiv muzlatilgan
- 4.2 Har chaqiruv joyi uchun bitta massiv
- 5. Xavfsiz HTML: html tegi
- 5.1 Birinchi variant
- 5.2 Brauzerda: hujum ishlamaydi
- 5.3 Ichma-ich shablonlar
- 5.4 Ekranlash yetmaydigan joylar
- Hujumchi nigohi
- 6. Ko'p uchraydigan xatolar
- 6.1 Tegni qavs bilan chaqirish
- 6.2 Indeksni adashtirish
- 6.3 Windows yo'li oddiy template literalda
- 6.4 Ikki marta ekranlash va "hamma joyga yetarli" deb o'ylash
- 7. Mashqlar
- 1-mashq (oson): Katta harf tegi
- 2-mashq (o'rta): Bo'sh qiymatlarni yashirish
- 3-mashq (qiyin): Chiroyli ko'p qatorli matn
- 4-mashq: Vazifalar qadami — html tegi alohida modulda
- 8. Real ishda
- Xulosa
- Manbalar
JavaScript tagged template literals: teg funksiyasi, String.raw va xavfsiz HTML
Qisqacha: Tagged template — backtick oldiga funksiya nomi yozilgan template literal:
html`<li>${izoh}</li>`. JavaScript bunday satrni o'zi yig'maydi. U matn bo'laklarini massiv qilib,${}qiymatlarini esa alohida argumentlar qilib funksiyaga beradi:html(qismlar, ...qiymatlar). Funksiya ulardan nima yasashni o'zi hal qiladi — masalan, har qiymatni ekranlab, XSS'dan himoyalangan HTML yasaydi.
Bu darsda
- Teg funksiyasi nima olishini (
qismlar,...qiymatlar) va ular qanday bo'linishini tushuntira olasiz. - Qismlar va qiymatlarni qayta yig'adigan o'z tegingizni yozasiz.
strings.rawvaString.rawbilan teskari chiziqni "boricha" olishni bilasiz.- Foydalanuvchi matnini avtomatik ekranlaydigan
htmltegini yozasiz va uning chegaralarini aytib bera olasiz. sql,css,gqlteglari real loyihalarda nima uchun ishlatilishini tushunasiz.
Oldin bilishingiz kerak: Template literals, Spread va rest operatori, Kontentni o'zgartirish va XSS xavfi, reduce.
1. Nega bu kerak?
Oldingi darsda Intl.Collator va Intl.Segmenter bilan matnni saraladik va bo'laklarga ajratdik. Bugun matnni yig'ish jarayonini qo'lga olamiz.
«Bahor» saytida "Mehmonlar fikri" bo'limi bor. Stajyor Sardor uni template literal bilan chizdi:
royxat.innerHTML = `<li><b>${ism}:</b> ${izoh}</li>`;Qisqa va o'qilishi oson. Lekin Kontentni o'zgartirish va XSS xavfi darsini eslang. izoh mehmon yozgan matn. Unda <img src="x" onerror="..."> bo'lsa, brauzer uni HTML deb o'qiydi va hujumchining kodi ishga tushadi. O'sha darsda ikki yo'l ko'rgan edik: textContent yoki har qiymatni ekranla funksiyasidan o'tkazish.
Ikkinchi yo'lning muammosi — unutish. Sardor o'nta joyda ${ekranla(izoh)} yozadi, o'n birinchisida esa shoshilib ${izoh} deb qo'yadi. Bitta unutilgan chaqiruv saytni ochib beradi.
Yaxshiroq bo'lardi, agar template literal o'zi har bir ${} qiymatini ekranlasa. Bu mumkin. Uning nomi — tagged template. Template literals darsida "backtick oldiga funksiya nomi yoziladi va u qiymatlarni xavfsiz qilishi mumkin" degan edik. 06-qismda esa styled.p`...` yozuvini ko'rgan edik (CSS-in-JS). Bugun o'sha va'dalarni bajaramiz.
Hayotdan o'xshatish: oshxonadagi kassir. Ofitsiant buyurtmani to'g'ridan-to'g'ri oshxonaga aytmaydi — avval kassaga beradi. Kassir har bir taomni tekshiradi, narxini qo'yadi, keyin oshxonaga uzatadi. Teg funksiyasi — ana shu kassir: template literalning har bir bo'lagi undan o'tadi.
2. Teg funksiyasi nima oladi
2.1 Birinchi teg
Tagged template (teg qo'yilgan shablon) — oldiga funksiya nomi yozilgan template literal. Bu funksiya teg (tag) deb ataladi. Qavs yozilmaydi: funksiya nomi va backtick yonma-yon turadi.
Teg nima olishini ko'rish uchun eng sodda funksiyani yozamiz. U hech narsa yig'maydi, faqat argumentlarini konsolga chiqaradi:
function korsat(qismlar, ...qiymatlar) {
console.log(qismlar);
console.log(qiymatlar);
}
const taom = "Osh";
const narx = 35000;
korsat`${taom} narxi: ${narx} so'm`;Konsolda:
[ '', ' narxi: ', " so'm" ]
[ 'Osh', 35000 ]korsat`...` — bu funksiya chaqiruvi. JavaScript template literalni ikkiga ajratdi:
- qismlar —
${}lar orasidagi oddiy matn bo'laklari. Ular har doim satr. - qiymatlar —
${}ichidagi ifodalarning natijalari. Ular satrga aylantirilmaydi:35000son bo'lib qoldi.
Birinchi argument — qismlar massivi. Qolgan argumentlar — qiymatlar, har biri alohida. Ularni bitta massivga yig'ish uchun rest parametri ...qiymatlar ishlatdik.
Pastdagi Keyingi tugmasini bosib, JavaScript literalni qanday "kesishini" qadamma-qadam kuzating:
2.2 Bo'linish qoidasi
Vizualdagi oxirgi gapga qayting: qismlar doim qiymatlardan bittaga ko'p. Sababi oddiy. Har bir ${} ning chap va o'ng tomonida matn bo'lagi bor — hatto u bo'sh bo'lsa ham. Ikkita ${} matnni uch bo'lakka ajratadi, xuddi ikkita kesish nonni uch bo'lakka bo'lgandek.
Shuning uchun literal ${} bilan boshlansa, birinchi qism — bo'sh satr ''. ${} bilan tugasa, oxirgi qism ham bo'sh. Hatto faqat ikkita qiymatdan iborat literal ham uchta qism beradi:
function korsat(qismlar, ...qiymatlar) {
console.log(qismlar.length, qiymatlar.length);
}
korsat`${1}${2}`;
korsat`Qiymatsiz matn`;Konsolda:
3 2
1 0Ikkinchi chaqiruvda ${} yo'q — bitta qism, nol qiymat. Qoida bu yerda ham ishlaydi: 0 + 1 = 1.
Bu qoida keyingi bo'limdagi yig'ish uchun kalit: qismlar[0], keyin qiymatlar[0], keyin qismlar[1], keyin qiymatlar[1] va oxirida yana bitta qism. Qism, qiymat, qism, qiymat, qism — doim qism bilan boshlanib, qism bilan tugaydi.
Tekshirib ko'ring:
korsat`Stol ${4}, soat ${"19:00"}.`chaqiruvidaqismlarvaqiymatlarnimaga teng?
Javob
qismlar — [ 'Stol ', ', soat ', '.' ], qiymatlar — [ 4, '19:00' ]. Ikkita ${} matnni uch bo'lakka ajratdi. Oxirgi qism — nuqta, chunki literal ${} bilan emas, . bilan tugadi.
2.3 Qayta yig'ish: o'z tegingiz
Teg qismlar va qiymatlarni oldi. Endi ulardan satr yasaymiz. Har qiymatni yig'ishdan oldin o'zgartirish mumkin — tegning butun kuchi shunda.
«Bahor» kassasi uchun chek tegini yozamiz. U har bir sonni uch xonadan ajratib, oxiriga "so'm" qo'shadi. Matn qismlariga tegmaydi:
function summaniYoz(qiymat) {
if (typeof qiymat !== "number") return qiymat;
const ajratilgan = qiymat
.toLocaleString("en-US")
.replaceAll(",", " ");
return `${ajratilgan} so'm`;
}
function chek(qismlar, ...qiymatlar) {
let natija = qismlar[0];
for (let i = 0; i < qiymatlar.length; i++) {
natija += summaniYoz(qiymatlar[i]) + qismlar[i + 1];
}
return natija;
}
const taom = "Osh";
console.log(chek`${taom}: ${35000}, jami: ${105000}`);Konsolda:
Osh: 35 000 so'm, jami: 105 000 so'mKodni qatorma-qator o'qiymiz:
natijabirinchi qism bilan boshlanadi — u har doim bor (bo'sh bo'lsa ham).- Sikl har bir qiymat uchun ikki narsa qo'shadi: o'zgartirilgan qiymat va undan keyingi qism. Shuning uchun
qismlar[i + 1]. taomson emas —summaniYozuni o'zgarishsiz qaytaradi. Sonlar esa formatlanadi.
toLocaleString("en-US") sonni 105,000 ko'rinishiga keltiradi, replaceAll esa vergulni bo'sh joyga almashtiradi. Formatlashning to'liq yo'li — Intl.NumberFormat darsida; bu yerda natija har kompyuterda bir xil chiqishi muhim edi.
2.4 Xuddi shu yig'ish — reduce bilan
Yuqoridagi sikl — har bir tegning qolipi. Uni reduce bilan qisqaroq yozish mumkin. Boshlang'ich qiymat berilmasa, reduce akkumulyatorni massivning birinchi elementi bilan boshlaydi va i = 1 dan aylanadi:
function birlashtir(qismlar, ...qiymatlar) {
return qismlar.reduce(
(natija, qism, i) => natija + qiymatlar[i - 1] + qism,
);
}
console.log(birlashtir`Stol ${4}, soat ${"19:00"}.`);Konsolda:
Stol 4, soat 19:00.i = 1 da qiymatlar[0] va qismlar[1] qo'shiladi, i = 2 da — qiymatlar[1] va qismlar[2]. Natija tegsiz template literal bilan bir xil. Bu birlashtir — "hech narsa qilmaydigan" teg. Uni asos qilib, qiymatlar[i - 1] o'rniga ekranla(qiymatlar[i - 1]) kabi istalgan ishlovni qo'yasiz.
Sikl yangi o'quvchiga tushunarliroq, reduce esa kutubxonalarda ko'proq uchraydi — biz ikkalasini ham ishlatamiz.
2.5 Teg satr qaytarishi shart emas
Teg — oddiy funksiya. U istalgan narsani qaytarishi mumkin: son, obyekt, hatto boshqa funksiya. Masalan, bu teg matnni emas, uning "tuzilmasini" qaytaradi:
function tuzilma(qismlar, ...qiymatlar) {
return { matnlar: [...qismlar], sonlar: qiymatlar.length };
}
console.log(tuzilma`Bron: ${"Dilshod aka"}, ${4} kishi`);Konsolda:
{ matnlar: [ 'Bron: ', ', ', ' kishi' ], sonlar: 2 }Bu imkoniyat ahamiyatli. «Hujumchi nigohi» bo'limidagi sql tegi aynan obyekt qaytaradi — satr emas. Shu bilan u xavfli birlashtirishning o'zini imkonsiz qiladi.
Tekshirib ko'ring: Teg funksiyasi
returnyozmasa,const x = korsat`salom`;dan keyinxnimaga teng?
Javob
undefined. Teg chaqiruvi — oddiy funksiya chaqiruvi. Funksiya return qilmasa, natija undefined (return va erta qaytish). Template literal o'zi hech qanday satr yasamaydi — buni teg qilishi kerak.
3. raw: teskari chiziqni boricha olish
3.1 Ikki xil qism: tayyor va xom
String asoslari darsidan bilasiz: satrda \n — yangi qator, \t — tabulyatsiya. JavaScript satrni o'qiyotganda bunday escape ketma-ketliklarini darhol haqiqiy belgiga aylantiradi.
Teg qismlarni ikki xil ko'rinishda oladi:
qismlar[i]— tayyor (cooked): escape'lar allaqachon belgiga aylangan;qismlar.raw[i]— xom (raw): kodda qanday yozilgan bo'lsa, shunday.\n— ikkita belgi:\van.
function ikkalasi(qismlar) {
console.log(qismlar[0].length, qismlar.raw[0].length);
console.log(qismlar.raw[0]);
}
ikkalasi`Osh\nManti`;Konsolda:
9 10
Osh\nMantiTayyor qismda 9 belgi: \n bitta yangi qator belgisiga aylandi. Xom qismda 10 belgi: \ va n alohida turibdi. raw — qismlar massivining xususiyati. U sanalmaydigan (Xususiyat deskriptorlari), shuning uchun console.log(qismlar) uni ko'rsatmaydi.
3.2 String.raw: tayyor teg
Xom matn shunchalik kerakki, JavaScript'da u uchun tayyor teg bor — String.raw. U qismlarning xom ko'rinishini qiymatlar bilan birlashtiradi:
const yil = 2026;
const yol = String.raw`C:\Users\Sardor\new\menyu-${yil}.txt`;
console.log(yol); // C:\Users\Sardor\new\menyu-2026.txtWindows yo'lidagi \U, \S, \n, \m — hech biri escape deb o'qilmadi. \n ham yangi qatorga aylanmadi. ${yil} esa odatdagidek qiymatga almashdi.
Bitta nozik joy bor. \ dan keyin darhol ${ kelsa, teskari chiziq dollarni ekranlaydi va ${...} oddiy matn bo'lib qoladi. Shuning uchun qiymatni papka nomining o'rtasiga qo'ydik, \${yil} deb emas.
Eng foydali joyi — regex. RegExp asoslari darsida new RegExp("\\d+") uchun teskari chiziqni ikki marta yozishga to'g'ri kelgan edi. String.raw bilan bitta yetarli:
const naqsh = new RegExp(String.raw`\+998 \d{2}`, "g");
const matn = "Tel: +998 90 000 00 00";
console.log(naqsh.source); // \+998 \d{2}
console.log(matn.match(naqsh)); // [ '+998 90' ]Naqsh literal /.../ dagidek o'qiladi, lekin satr sifatida yig'ilyapti. Demak, uning ichiga ${} bilan qiymat qo'shsa ham bo'ladi.
Diqqat:
String.rawichiga foydalanuvchi matnini${}bilan qo'yib regex yasash xavfli: matndagi.yoki+regex buyrug'iga aylanadi. Xavfsiz yo'li — Almashtirish chuqur va xavfsiz dinamik regex darsidagiRegExp.escape.
3.3 Noto'g'ri escape: tegsiz — xato, teg bilan — undefined
\u — Unicode escape boshlanishi. Undan keyin to'rtta o'n oltilik raqam kutiladi: \u0041 — A. Papka nomi kichik u bilan boshlansa, Windows yo'lida aynan \u paydo bo'ladi: C:\users. Undan keyin sers keladi — bu o'n oltilik raqamlar emas. Tegsiz template literal buni xato deb hisoblaydi:
const yol = `C:\users\sardor`;SyntaxError: Invalid Unicode escape sequenceTarjimasi: "Noto'g'ri Unicode escape ketma-ketligi." Kod ishga tushmaydi ham — xato o'qish bosqichida chiqadi. Katta \U esa xato bermaydi: u escape emas va oddiy U harfiga aylanadi.
Teg qo'yilgan literalda esa bunday escape xato emas (ES2018 dan beri). Tayyor qism undefined bo'ladi, xom qism esa saqlanadi:
function korsat(qismlar) {
console.log(qismlar[0], qismlar.raw[0]);
}
korsat`C:\users\sardor`;
console.log(String.raw`C:\users\sardor`); // C:\users\sardorKonsolda:
undefined C:\users\sardor
C:\users\sardorMantiqi shunday: tegsiz literalda JavaScript satrni o'zi yasashi kerak va noto'g'ri escape'ni hech narsaga aylantira olmaydi. Teg qo'yilganda esa satrni teg yasaydi. Unga xom matn kerak bo'lishi mumkin — shuning uchun JavaScript xato bermaydi, faqat tayyor ko'rinishni undefined qiladi.
Tekshirib ko'ring:
String.raw`a\tb`.lengthnimaga teng?`a\tb`.length-chi?
Javob
Birinchisi — 4: a, \, t, b. String.raw escape'ni aylantirmaydi. Ikkinchisi — 3: \t bitta tabulyatsiya belgisiga aylandi.
4. Qismlar massivi haqida ikki nozik fakt
4.1 Massiv muzlatilgan
Teg olgan qismlar massivi — muzlatilgan. Unga yozib bo'lmaydi:
function tekshir(qismlar) {
console.log(Object.isFrozen(qismlar), Object.isFrozen(qismlar.raw));
}
tekshir`Osh ${1} so'm`;Konsolda:
true trueBu — himoya. Qismlar kodning o'zidan olingan, ya'ni dasturchi yozgan matn. Teg ularni o'zgartira olmasligi kerak.
4.2 Har chaqiruv joyi uchun bitta massiv
Ikkinchi fakt kutilmaganroq. Kodning bitta joyidagi teg har safar aynan o'sha qismlar massivini oladi. Qiymatlar har safar yangi, qismlar esa — bir xil obyekt:
function qismlarniOl(qismlar) {
return qismlar;
}
function narxQatori(narx) {
return qismlarniOl`Narx: ${narx}`;
}
console.log(narxQatori(35000) === narxQatori(28000)); // true
const birinchi = qismlarniOl`Narx: ${1}`;
const ikkinchi = qismlarniOl`Narx: ${1}`;
console.log(birinchi === ikkinchi); // falseBirinchi console.log da narxQatori ikki marta chaqirildi, lekin ichidagi teg kodning bitta joyida turibdi. Shuning uchun ikkala chaqiruv === bilan teng massiv oldi (Havola semantikasi). birinchi va ikkinchi da esa ikkita alohida literal — kodning ikki joyi. Matni bir xil bo'lsa ham, massivlari har xil.
Bu nimaga kerak? Shablonni keshlash (bir marta hisoblangan natijani eslab qolish) uchun. Lit kutubxonasi (3.3 versiya) bitta html`...` shablonini birinchi marta tahlil qiladi va natijani qismlar massivi kaliti ostida eslab qoladi. Keyingi chaqiruvlarda faqat o'zgargan qiymatlarni yangilaydi. Lit'ni hozir bilish shart emas.
5. Xavfsiz HTML: html tegi
5.1 Birinchi variant
Endi «Nega bu kerak?» bo'limidagi masalaga qaytamiz. XSS darsidagi ekranla funksiyasini olamiz. U beshta maxsus belgini entity'ga almashtiradi — & birinchi, aks holda keyingi entity'lar buziladi. Keyin uni teg ichida har bir qiymatga qo'llaymiz:
function ekranla(matn) {
return String(matn)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function html(qismlar, ...qiymatlar) {
return qismlar.reduce(
(natija, qism, i) => natija + ekranla(qiymatlar[i - 1]) + qism,
);
}
const izoh = '<img src="x" onerror="alert(1)">';
console.log(html`<li><b>Mehmon:</b> ${izoh}</li>`);Konsolda:
<li><b>Mehmon:</b> <img src="x" onerror="alert(1)"></li>Natijaga qarang. <li> va <b> teglari tegilmadi — ular qismlarda, ya'ni dasturchi yozgan matnda. Hujumchining <img> i esa qiymatda edi va ekranlandi. Teg ikkalasini avtomatik farqlaydi: qism — ishonchli, qiymat — begona.
String(matn) nega kerak? Qiymat son bo'lishi mumkin: ${35000}. Sonda replaceAll metodi yo'q. String uni avval satrga aylantiradi.
Endi Sardor ${ekranla(izoh)} yozishni unuta olmaydi — u umuman yozmaydi. html tegini qo'ygan har bir joyda ekranlash avtomatik.
5.2 Brauzerda: hujum ishlamaydi
Xuddi shu tegni haqiqiy sahifada sinab ko'ramiz. Ikki izohdan biri — hujum. Agar u ishlasa, konsolda XSS yozuvi chiqadi:
<ul id="izohlar"></ul>
<script>
function ekranla(matn) {
return String(matn)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function html(qismlar, ...qiymatlar) {
return qismlar.reduce(
(natija, qism, i) => natija + ekranla(qiymatlar[i - 1]) + qism,
);
}
const izohlar = [
"Osh zo'r, xizmat a'lo!",
'<img src="x" onerror="console.log(\'XSS\')">',
];
const royxat = document.querySelector("#izohlar");
royxat.innerHTML = izohlar
.map((izoh) => html`<li><b>Mehmon:</b> ${izoh}</li>`)
.join("");
console.log(royxat.children.length);
console.log(royxat.querySelectorAll("img").length);
</script>Konsolda:
2
0Ikkita li chizildi, img esa bitta ham yo'q — hujumchining yozuvi ekranda oddiy harflar bo'lib turibdi. <b>Mehmon:</b> esa qalin. "Tahrirlab ko'rish" ni bosing va html so'zini o'chirib, oddiy template literal qiling. Konsolda XSS chiqadi va img soni 1 bo'ladi.
5.3 Ichma-ich shablonlar
Birinchi variantda bitta muammo bor. Ro'yxatni tegning ichida yasashga urinib ko'raylik:
function ekranla(matn) {
return String(matn)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">");
}
function html(qismlar, ...qiymatlar) {
return qismlar.reduce(
(natija, qism, i) => natija + ekranla(qiymatlar[i - 1]) + qism,
);
}
const taomlar = ["Osh", "Manti"];
const ichki = taomlar.map((taom) => html`<li>${taom}</li>`);
console.log(html`<ul>${ichki}</ul>`);Konsolda:
<ul><li>Osh</li>,<li>Manti</li></ul>Ikki xato birdaniga. Birinchidan, ichki html allaqachon xavfsiz qilgan <li> teglar ikkinchi marta ekranlandi va endi harflar bo'lib ko'rinadi. Ikkinchidan, massiv satrga aylanganda elementlari vergul bilan ulandi (massivni matnga aylantirish).
Teg "bu satr allaqachon xavfsiz" ekanini bilishi kerak. Oddiy satrda bunday belgi yo'q. Shuning uchun html endi oddiy satr emas, maxsus klass nusxasini qaytaradi. Teg qiymatni ekranlashdan oldin tekshiradi: bu nusxa bo'lsa — ekranlamaydi, massiv bo'lsa — har elementini tayyorlab, bo'sh satr bilan ulaydi:
class XavfsizHtml {
constructor(matn) {
this.matn = matn;
}
toString() {
return this.matn;
}
}
function ekranla(matn) {
return String(matn)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function tayyorla(qiymat) {
if (qiymat instanceof XavfsizHtml) return qiymat.matn;
if (Array.isArray(qiymat)) return qiymat.map(tayyorla).join("");
return ekranla(qiymat);
}
function html(qismlar, ...qiymatlar) {
const matn = qismlar.reduce(
(natija, qism, i) => natija + tayyorla(qiymatlar[i - 1]) + qism,
);
return new XavfsizHtml(matn);
}
const taomlar = ["Osh", "Manti <yangi>"];
const ichki = taomlar.map((taom) => html`<li>${taom}</li>`);
console.log(String(html`<ul>${ichki}</ul>`));Konsolda:
<ul><li>Osh</li><li>Manti <yangi></li></ul>Ichki <li> lar tirik, vergul yo'q, <yangi> esa baribir ekranlandi — chunki u ichki shablonning qiymati edi. toString metodi nusxani kerak joyda satrga aylantiradi: String(...), innerHTML = ... yoki boshqa template literal ichida (class sintaksisi).
XavfsizHtml — "tekshirilgan" degan muhr. Muhrni faqat html tegi qo'yadi. Shuning uchun begona satr muhrsiz keladi va doim ekranlanadi.
Tekshirib ko'ring: Yuqoridagi tegda
html`<p>${new XavfsizHtml(izoh)}</p>`deb yozilsa vaizoh— mehmon matni bo'lsa, nima bo'ladi?
Javob
XSS. tayyorla XavfsizHtml nusxasini ekranlamaydi — u muhrga ishonadi. Muhrni qo'lda, begona matnga bossangiz, himoya o'chadi. Shuning uchun real kutubxonalarda bunday "ishonchli HTML" yasash funksiyasining nomi qo'rqinchli qilib qo'yiladi (React'da dangerouslySetInnerHTML — "xavfli"), toki ishlatgan odam o'ylab ko'rsin.
5.4 Ekranlash yetmaydigan joylar
html tegi juda ko'p holatni yopadi, lekin hammasini emas. Ekranlash qiymat matn yoki qo'shtirnoqli atribut ichiga tushganda ishlaydi. Uchta xavfli joy qoladi:
hrefvasrc:html`<a href="${havola}">`dahavolajavascript:alert(1)bo'lsa, bu satrda ekranlanadigan belgi yo'q. Bosilganda kod ishlaydi. Havolani alohida tekshirish kerak (https:bilan boshlanadimi).- Qo'shtirnoqsiz atribut:
html`<td class=${sinf}>`dasinfichidagi bo'sh joy yangi atribut ochadi:x onclick=.... Atribut qiymati doim qo'shtirnoq ichida bo'lsin. <script>va<style>ichi: u yerda HTML qoidalari emas, JavaScript va CSS qoidalari ishlaydi. Begona qiymatni u yerga qo'ymang.
Bu holatlarni to'liq XSS va DOM xavfsizligi darsida ko'ramiz. Real loyihalarda o'z tegingiz o'rniga tekshirilgan vositalar ishlatiladi: Lit 3.3-bob — html tegi bilan, DOMPurify 3.4-bob — tayyor HTML'ni tozalash uchun. Bugungi teg — ularning ichida nima borligini tushunish uchun.
Hujumchi nigohi
«Bahor»ning bron tizimi serverda ma'lumotlar bazasidan mehmonni ism bo'yicha qidiradi. Bazaga savol SQL tilida yoziladi — ma'lumotlar bazasining so'rov tili (26-qismda o'rganamiz, hozir bilish shart emas). Sardorning birinchi variantida so'rov template literal bilan yasalgan:
const ism = "x' OR '1'='1";
const sorov = `SELECT * FROM bronlar WHERE ism = '${ism}'`;
console.log(sorov);Konsolda:
SELECT * FROM bronlar WHERE ism = 'x' OR '1'='1'Hujumchi formaga ism o'rniga x' OR '1'='1 yozdi. Uning qo'shtirnog'i Sardorning qo'shtirnog'ini yopdi. Keyin OR '1'='1' qo'shildi — bu shart har doim rost. Natijada baza barcha mehmonlarning bronlarini qaytaradi: ismlar, telefonlar. Bu hujumning nomi — SQL injection (SQL ga kod "ukol qilish"). U OWASP Top 10 ro'yxatida — veb-ilovalarning eng xavfli zaifliklari orasida — ko'p yillardan beri turibdi.
Himoya — qiymatni so'rov matniga umuman qo'shmaslik. Parametrlangan so'rov (parameterized query): so'rov matnida qiymat o'rniga $1, $2 belgilari turadi, qiymatlar esa bazaga alohida ro'yxat bo'lib boradi. Baza ularni hech qachon kod deb o'qimaydi. sql tegi aynan shuni yasaydi:
function sql(qismlar, ...qiymatlar) {
const matn = qismlar.reduce(
(natija, qism, i) => `${natija}$${i}${qism}`,
);
return { matn, qiymatlar };
}
const ism = "x' OR '1'='1";
const sorov = sql`SELECT * FROM bronlar WHERE ism = ${ism}`;
console.log(sorov.matn);
console.log(sorov.qiymatlar);Konsolda:
SELECT * FROM bronlar WHERE ism = $1
[ "x' OR '1'='1" ]`${natija}$${i}${qism}` — birinchi $ oddiy dollar belgisi, undan keyingi ${i} — qiymat. Shunday qilib $1, $2 hosil bo'ladi.
Hujumchining matni so'rovga kirmadi. U alohida ro'yxatda turibdi va baza uni "ism shu satrga teng bo'lgan qator" deb qidiradi — bunday ism yo'q, natija bo'sh. Teg satr emas, obyekt qaytargani uchun dasturchi uni xato bilan oddiy satrdek ishlatib ham yubora olmaydi.
postgres kutubxonasining sql`...` tegi xuddi shu tamoyilda ishlaydi. Server va bazani 25–27-qismlarda o'rganamiz. Bugungi saboq: qiymatni kodga yopishtirmang — teg yoki parametr orqali alohida bering. Bu qoida HTML'da ham (html tegi), SQL'da ham bir xil.
6. Ko'p uchraydigan xatolar
6.1 Tegni qavs bilan chaqirish
function html(qismlar, ...qiymatlar) {
return qismlar.reduce(
(natija, qism, i) => natija + qiymatlar[i - 1] + qism,
);
}
const taom = "Osh";
html(`<b>${taom}</b>`);TypeError: qismlar.reduce is not a functionTarjimasi: "qismlar.reduce funksiya emas." html(`...`) — oddiy chaqiruv. Template literal avval o'zi yig'ildi va html ga bitta tayyor satr keldi. Satrda reduce metodi yo'q. Xato reduce so'zini ko'rsatyapti, lekin aybdor qavs. Tuzatish: qavssiz yozing — html`<b>${taom}</b>`. Eng yomoni: tegingiz reduce emas, boshqa narsa ishlatsa, xato chiqmasligi va ekranlash jimgina o'tkazib yuborilishi mumkin.
6.2 Indeksni adashtirish
Siklda natija += qiymatlar[i] + qismlar[i] deb yozilsa, birinchi qism ikki marta qo'shiladi, oxirgisi esa yo'qoladi. Qoida: natija qismlar[0] bilan boshlanadi, siklda — qiymatlar[i] va qismlar[i + 1]. reduce variantida — qiymatlar[i - 1] va qism.
6.3 Windows yo'li oddiy template literalda
`C:\users\sardor` — SyntaxError, `C:\new` esa jimgina yangi qatorga aylanadi. Tuzatish: String.raw yoki teskari chiziqni ikki marta yozing.
6.4 Ikki marta ekranlash va "hamma joyga yetarli" deb o'ylash
Ichma-ich tegda muhr bo'lmasa, ekranda <li> chiqadi — ekranlash bir marta, eng oxirida bo'lsin. Teg esa href dagi javascript: ni, qo'shtirnoqsiz atributni va <script> ichini himoya qilmaydi — «Ekranlash yetmaydigan joylar» bo'limiga qayting.
7. Mashqlar
1-mashq (oson): Katta harf tegi
katta tegini yozing: u qismlarga tegmasin, har bir qiymatni esa katta harfga aylantirsin. Ishora: String(qiymat).toUpperCase() (Matnni o'zgartirish) va «Qayta yig'ish: o'z tegingiz» bo'limidagi sikl qolipi — natija birinchi qismdan boshlanadi.
const ism = "Dilshod aka";
console.log(katta`Xush kelibsiz, ${ism}! Stol ${4}.`);
// Xush kelibsiz, DILSHOD AKA! Stol 4.Yechim
function katta(qismlar, ...qiymatlar) {
let natija = qismlar[0];
for (let i = 0; i < qiymatlar.length; i++) {
natija += String(qiymatlar[i]).toUpperCase() + qismlar[i + 1];
}
return natija;
}
const ism = "Dilshod aka";
console.log(katta`Xush kelibsiz, ${ism}! Stol ${4}.`);Konsolda:
Xush kelibsiz, DILSHOD AKA! Stol 4.4 son edi — String uni satrga aylantirdi, toUpperCase raqamga ta'sir qilmadi. "Xush kelibsiz" esa qism, u o'zgarmadi.
2-mashq (o'rta): Bo'sh qiymatlarni yashirish
Shablonda ba'zan qiymat bo'lmaydi: ${izoh} — undefined, ${chegirma} — null. Oddiy literal ularni "undefined" va "null" deb yozib qo'yadi. toza tegini yozing: undefined, null va false qiymatlar o'rniga bo'sh satr qo'ysin, qolganlari odatdagidek. Ishora: «Xuddi shu yig'ish — reduce bilan» bo'limidagi qolip va ?? operatori — lekin false uchun alohida shart kerak.
const izoh = undefined;
console.log(toza`Bron: ${"Dilshod aka"}${izoh}, VIP: ${false}`);
// Bron: Dilshod aka, VIP: Yechim
function bosmiYoqmi(qiymat) {
return qiymat === undefined || qiymat === null || qiymat === false;
}
function toza(qismlar, ...qiymatlar) {
return qismlar.reduce((natija, qism, i) => {
const qiymat = qiymatlar[i - 1];
return natija + (bosmiYoqmi(qiymat) ? "" : qiymat) + qism;
});
}
const izoh = undefined;
const natija = toza`Bron: ${"Dilshod aka"}${izoh}, VIP: ${false}`;
console.log(JSON.stringify(natija)); // "Bron: Dilshod aka, VIP: "JSON.stringify natijani qo'shtirnoq ichida ko'rsatadi — oxiridagi bo'sh joy ko'rinib turishi uchun. 0 va "" esa yashirilmadi: 0 — haqiqiy son (masalan, "0 ta bo'sh stol"). ?? faqat null va undefined ni ushlagani uchun false ni alohida tekshirdik. Lit kutubxonasi ham null, undefined va false ni xuddi shunday hech narsa chizmaydi.
3-mashq (qiyin): Chiroyli ko'p qatorli matn
Template literalda ko'p qatorli matn kod bilan birga suriladi (Template literals darsidagi bo'sh joy tuzog'i). Shu muammo uchun tekisla tegini yozing. U avval qiymatlarni joyiga qo'yib matnni yig'sin, keyin birinchi va oxirgi bo'sh qatorlarni olib tashlasin va har qatordagi umumiy chap surilishni kessin. Ishora: split("\n"), filter va Math.min bilan eng kichik surilishni toping; surilishni regex /^ */ bilan o'lchang (RegExp asoslari).
function xabarYasa(ism) {
return tekisla`
Hurmatli ${ism}!
Broningiz tasdiqlandi.
«Bahor»
`;
}
console.log(xabarYasa("Dilshod aka"));Yechim
function tekisla(qismlar, ...qiymatlar) {
const matn = qismlar.reduce(
(natija, qism, i) => natija + qiymatlar[i - 1] + qism,
);
const qatorlar = matn.split("\n");
if (qatorlar[0].trim() === "") qatorlar.shift();
if (qatorlar.at(-1).trim() === "") qatorlar.pop();
const surilishlar = qatorlar
.filter((qator) => qator.trim() !== "")
.map((qator) => qator.match(/^ */)[0].length);
const eng = Math.min(...surilishlar);
return qatorlar.map((qator) => qator.slice(eng)).join("\n");
}
function xabarYasa(ism) {
return tekisla`
Hurmatli ${ism}!
Broningiz tasdiqlandi.
«Bahor»
`;
}
console.log(xabarYasa("Dilshod aka"));Konsolda:
Hurmatli Dilshod aka!
Broningiz tasdiqlandi.
«Bahor»Eng kichik surilish — 4 ta bo'sh joy. Har qatordan 4 ta kesildi, ikkinchi qatorning qo'shimcha ikkitasi esa saqlandi. Bo'sh qatorlar surilish hisobiga kirmadi: aks holda Math.min 0 ni topib, hech narsani kesmasdi. npm'da dedent nomli paket aynan shu ishni qiladi.
4-mashq: Vazifalar qadami — html tegi alohida modulda
vazifalar ning kanonik kodi DOM'ni createElement va textContent bilan quradi — bu eng xavfsiz yo'l, uni o'zgartirmaymiz. Bugungi tegni esa mashqlar repoda modul qilib saqlaymiz: kurs/mashqlar/12/19-teglar/html.mjs. Undan XavfsizHtml, ekranla va html ni eksport qiling («Ichma-ich shablonlar» bo'limidagi variant). Yonida sinash.mjs yasang: u html ni import qilib, vazifalar ro'yxatidan <ul> yasasin. Bitta vazifa matni — "<b>Non</b> olish". U ekranlanganini tekshiring.
Yechim
// 12/19-teglar/html.mjs
export class XavfsizHtml {
constructor(matn) {
this.matn = matn;
}
toString() {
return this.matn;
}
}
export function ekranla(matn) {
return String(matn)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function tayyorla(qiymat) {
if (qiymat instanceof XavfsizHtml) return qiymat.matn;
if (Array.isArray(qiymat)) return qiymat.map(tayyorla).join("");
return ekranla(qiymat);
}
export function html(qismlar, ...qiymatlar) {
const matn = qismlar.reduce(
(natija, qism, i) => natija + tayyorla(qiymatlar[i - 1]) + qism,
);
return new XavfsizHtml(matn);
}// 12/19-teglar/sinash.mjs
import { html } from "./html.mjs";
const vazifalar = [
{ id: 1, matn: "<b>Non</b> olish", bajarildi: false },
{ id: 2, matn: "Onamga qo'ng'iroq qilish", bajarildi: true },
];
const royxat = html`<ul>${vazifalar.map(
(v) => html`<li data-id="${v.id}">${v.matn}</li>`,
)}</ul>`;
console.log(String(royxat));Terminalda node 12/19-teglar/sinash.mjs:
<ul><li data-id="1"><b>Non</b> olish</li><li data-id="2">Onamga qo'ng'iroq qilish</li></ul><b> ekranlandi, apostrof esa ' ga aylandi — brauzer uni baribir ' deb ko'rsatadi. data-id qiymati qo'shtirnoq ichida — «Ekranlash yetmaydigan joylar» bo'limidagi qoida.
git add 12/19-teglar
git commit -m "12/19: html tegi va uni sinash"8. Real ishda
Tagged template'ni o'zingiz kam yozasiz, lekin tayyorlarini tez-tez uchratasiz. Kutubxona nomlarini eslab qolish shart emas — tanish shaklni ko'rib, "bu teg" deyishingiz yetarli:
| Teg | Qayerda | Nima qiladi |
|---|---|---|
html`...` |
Lit 3.3 | xavfsiz va keshlangan HTML |
css`...`, styled.p`...` |
styled-components 6.5 | CSS'dan klass yasaydi |
gql`...` |
graphql-tag 2.12 | GraphQL so'rovini tahlil qiladi |
sql`...` |
postgres 3.4 | parametrlangan SQL so'rovi |
- Web Components va Lit. Lit butunlay
htmlvacssteglariga qurilgan. Web Components'ni 13-qismda o'rganamiz.gql— GraphQL degan so'rov tilining tegi; uni ham keyinroq ko'rasiz. - Ma'lumotlar bazasi.
postgres, Prisma'ning$queryRawva Drizzle'ningsqlteglari so'rovni parametrlangan holda yuboradi. Code review'da tegsiz`SELECT ... ${id}`ni ko'rsangiz — darhol savol bering. - Intervyu. "Tagged template nima qaytaradi?", "
String.rawnima uchun kerak?", "Qismlar va qiymatlar soni qanday bog'liq?" — middle darajadagi savollar.
Xulosa
- Tagged template —
teg`...`: JavaScript literalni o'zi yig'maydi, uniteg(qismlar, ...qiymatlar)ga beradi. - Qismlar doim qiymatlardan bittaga ko'p; yig'ish —
qismlar[0], keyin har qiymat va undan keyingi qism. qismlar.raw— xom matn;String.raw— escape'larni aylantirmaydigan tayyor teg (Windows yo'li, regex).- Qismlar massivi muzlatilgan va bitta chaqiruv joyi uchun doim bir xil — kutubxonalar shu bilan keshlaydi.
htmltegi har qiymatni avtomatik ekranlaydi; ichma-ich shablonlar uchun muhr kerak;href, qo'shtirnoqsiz atribut va<script>ichi uchun ekranlash yetmaydi.- Qiymatni kodga yopishtirmang:
sqltegi so'rov matni va qiymatlarni alohida beradi — SQL injection'ga yo'l yo'q.
Keyingi dars: Proxy va Reflect — bugun template literalni "ushlab qolgan" bo'lsak, keyingi darsda obyekt bilan bo'ladigan har bir amalni — o'qish, yozish, o'chirishni — ushlab qolishni o'rganamiz.
Manbalar
- MDN: "Template literals (Template strings)" — "Tagged templates" va "Raw strings" bo'limlari, "String.raw()" — developer.mozilla.org
- ECMAScript 2025 Language Specification, 13.2.8 "Template Literals" va 13.3.11 "Tagged Templates" — tc39.es/ecma262
- TC39: "Template Literal Revision" (ES2018, noto'g'ri escape'lar teg ichida) — github.com/tc39/proposal-template-literal-revision
- OWASP: "SQL Injection Prevention Cheat Sheet" — cheatsheetseries.owasp.org
- Lit: "Expressions" (Templates) — lit.dev/docs
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!