IlmHamroh
JavaScript Full-stack/9-qism. JavaScript brauzerda va debugging12/29-dars25 daqiqa
Mundarija (40)

JavaScript hodisalari: addEventListener asoslari, removeEventListener va this

Qisqacha: Tugma bosilganda kod ishlashi uchun elementga tinglovchi biriktiriladi: tugma.addEventListener("click", funksiya). Funksiyani o'zingiz chaqirmaysiz — brauzer uni hodisa yuz berganda chaqiradi. Tinglovchini olib tashlash uchun removeEventListener ga aynan o'sha funksiyani berasiz. onclick xususiyati faqat bitta funksiyani saqlaydi, shuning uchun yangi kodda addEventListener ishlatiladi.

Bu darsda

  • Tugma bosilganda ishlaydigan kodni addEventListener bilan yoza olasiz.
  • Bir nechta tugmaga sikl bilan tinglovchi qo'yasiz va bosilgan tugmaning kartasini topasiz.
  • Tinglovchi qachon element bilan qolishini va qachon yo'qolishini (ko'chirish, nusxa, innerHTML) bilasiz.
  • removeEventListener nega ba'zan "ishlamasligini" tushuntira olasiz.
  • onclick xususiyati, onclick="..." atributi va addEventListener farqini bilasiz.
  • Handler ichida this nimaga teng ekanini va arrow funksiyada nega boshqacha ekanini bilasiz.

Oldin bilishingiz kerak: Element yaratish, joylash va o'chirish, Callback, Holatdan UI chizish.

1. Nega bu kerak?

O'tgan darslarda sahifani JavaScript bilan o'zgartirdingiz: matn yozdingiz, class qo'ydingiz, element yaratdingiz. Mehmon esa sahifani shunchaki ko'rmaydi — u bosadi, yozadi, suradi. Sayt shunga javob berishi kerak.

Bir necha darsda biz tugma.addEventListener("click", ...) qatorini tayyor qolip sifatida ishlatdik va har safar "to'liq keyin o'rganamiz" dedik. Bugun o'sha "keyin" keldi: qolip qanday ishlaydi, qanday olib tashlanadi va qayerda jimgina buziladi.

Hayotdan o'xshatish: choyxona eshigida qo'ng'iroq bor. Ofitsiant Otabek eshik oldida turmaydi — u ishini qiladi. Qo'ng'iroq jiringlasa, eshikka boradi. Otabek qo'ng'iroqni tinglaydi, jiringlash esa hodisa.

Brauzerda ham shunday. Hodisa (event) — "shu narsa yuz berdi" degan xabar: tugma bosildi, maydonga harf yozildi, sahifa yuklandi. Bu so'zni Brauzer muhiti darsida DOMContentLoaded bilan ko'rgansiz. Hodisa tinglovchisi (event listener) — hodisa yuz berganda brauzer chaqiradigan funksiya. Ko'pincha uni qisqa qilib handler ham deyishadi — "ishlovchi".

2. Birinchi tinglovchi

2.1 Tugma bosilganda

Quyidagi oynada «Savatga» tugmasini bosing va konsolga qarang:

html
<button id="savatga" type="button">Savatga</button>
<script>
  const tugma = document.getElementById("savatga");

  function savatgaQosh() {
    console.log("Osh savatga qo'shildi");
  }

  tugma.addEventListener("click", savatgaQosh);
</script>

Sahifa ochilganda konsol bo'sh. Har bosishda bitta qator chiqadi: "Osh savatga qo'shildi". Uch marta bossangiz — uch qator.

addEventListener ikki argument oladi:

  1. Hodisa turi — satr: "click" (bosish). Kichik harf bilan va on qo'shimchasisiz.
  2. Funksiya — hodisa yuz berganda bajariladigan kod.

O'qilishi: "tugma da click hodisasi bo'lsa, savatgaQosh ni chaqir".

2.2 Funksiyani biz emas, brauzer chaqiradi

E'tibor bering: savatgaQosh dan keyin qavs yo'q. Biz funksiyani chaqirmayapmiz — uni brauzerga beryapmiz. Brauzer uni eslab qoladi va har bosishda o'zi chaqiradi. Bu callback — 08-qismda massiv bilan ko'rgan g'oya. O'sha darsda "brauzer hodisalari ham callback qabul qiladi" degan edik — mana o'sha.

Jarayonni diagrammada ko'ring. Kod faqat bir marta — sahifa ochilganda ishlaydi va tinglovchini ro'yxatga yozadi. Keyin brauzer kutadi:

sequenceDiagram
  participant K as Kod
  participant B as Brauzer
  participant M as Mehmon
  K->>B: addEventListener("click", savatgaQosh)
  Note over B: tugmaning ro'yxatiga yozdi
  M->>B: tugmani bosdi
  B->>K: savatgaQosh() ni chaqirdi
  M->>B: yana bosdi
  B->>K: yana chaqirdi

Diagrammadagi asosiy fikr: addEventListener qatori tugma bosilishini kutib turmaydi. U bir lahzada bajariladi va skript keyingi qatorga o'tadi. Funksiya keyinroq, mehmon bosganda ishlaydi — balki bir soniyadan keyin, balki hech qachon.

Tekshirib ko'ring: Quyidagi kodda konsolga nechta qator va qaysi tartibda chiqadi (tugma hali bosilmagan)?

console.log("A"); tugma.addEventListener("click", () => console.log("B")); console.log("C");

Javob

Ikki qator: A, keyin C. B sahifa ochilganda chiqmaydi — u faqat tugma bosilganda chiqadi. addEventListener funksiyani chaqirmaydi, faqat "bosilganda chaqir" deb ro'yxatga yozadi.

2.3 Funksiyani joyida yozish

Funksiya faqat shu tugma uchun kerak bo'lsa, unga alohida nom berish shart emas. Uni to'g'ridan-to'g'ri ikkinchi argumentga yozish mumkin — arrow funksiya bilan (Arrow funksiyalar):

html
<button id="choy" type="button">Ko'k choy</button>
<p id="holat">Buyurtma yo'q</p>
<script>
  const tugma = document.getElementById("choy");
  const holat = document.getElementById("holat");
  let soni = 0;

  tugma.addEventListener("click", () => {
    soni++;
    holat.textContent = `Ko'k choy: ${soni} ta`;
  });
</script>

Tugmani bosing: matn "Ko'k choy: 1 ta", "Ko'k choy: 2 ta" bo'lib o'zgaradi. Bu safar natija konsolda emas, sahifaning o'zida — hodisa va DOM o'zgartirish birga ishlayapti.

Bu shakl sizga tanish: o'tgan o'n bir darsda "tayyor qolip" deb ishlatganimiz aynan shu edi. Endi uning har bo'lagini o'qiy olasiz:

  • tugma — tinglovchi biriktiriladigan element;
  • "click" — hodisa turi;
  • () => { ... } — brauzer keyinroq, har bosishda chaqiradigan funksiya.

soni o'zgaruvchisi funksiyadan tashqarida e'lon qilingan. Shuning uchun u bosishlar orasida saqlanib qoladi. Agar let soni = 0 ni funksiya ichiga ko'chirsangiz, har bosishda noldan boshlanadi va doim "1 ta" chiqadi — "Tahrirlab ko'rish" bilan sinang.

Qaysi biri yaxshiroq — nomli funksiyami yoki joyida yozilgani? Qisqa va bir martalik ish uchun joyida yozish qulay. Funksiya ikki joyda kerak bo'lsa yoki keyin olib tashlanishi kerak bo'lsa — nomli funksiya. Ikkinchisining sababini «removeEventListener» bo'limida ko'rasiz.

3. Bitta elementga bir nechta tinglovchi

addEventListener tinglovchini qo'shadi, eskisini o'chirmaydi. Bitta tugmaga bir nechta funksiya biriktirish mumkin:

html
<button id="bron" type="button">Stol band qilish</button>
<script>
  const tugma = document.getElementById("bron");

  function hisobla() {
    console.log("1: bosishlar hisobiga qo'shildi");
  }
  function xabarBer() {
    console.log("2: Otabekka xabar ketdi");
  }

  tugma.addEventListener("click", hisobla);
  tugma.addEventListener("click", xabarBer);
  tugma.addEventListener("click", hisobla);
</script>

Tugmani bir marta bosing. Konsolda ikki qator chiqadi, aynan shu tartibda: avval "1: …", keyin "2: …". Ikki xulosa:

  • Tinglovchilar qo'shilgan tartibda chaqiriladi.
  • Bitta funksiyani bir elementga ikki marta qo'shib bo'lmaydi. Uchinchi qator hisobla ni qayta qo'shmoqchi bo'ldi — brauzer "bu allaqachon bor" deb jim o'tkazib yubordi. Shuning uchun "1: …" bir marta chiqdi.

Lekin bu faqat bir xil funksiya uchun. Har safar yangi arrow funksiya yozsangiz — ular har xil funksiyalar, hammasi qo'shiladi.

4. Hodisa turlari bilan tanishuv

"click" — eng ko'p ishlatiladigan tur, lekin yagonasi emas. Keyingi darslarda har biri alohida o'rganiladi. Hozircha ularning nomlarini tanib qo'ying:

Hodisa Qachon Darsi
click bosish (sichqoncha, barmoq, Enter) shu dars
dblclick, pointerdown ikki marta bosish, barmoq tegishi Pointer hodisalari
keydown klaviatura tugmasi bosildi Klaviatura
focus, scroll fokus keldi, sahifa surildi Fokus va scroll
input, submit maydonga yozildi, forma yuborildi Forma hodisalari
DOMContentLoaded, load DOM tayyor, hammasi yuklandi Brauzer muhiti

Bitta muhim fakt: click faqat sichqoncha emas. Tugmaga Tab bilan borib Enter yoki Bo'sh joy bossangiz ham, brauzer click hodisasini yuboradi. Biz buni Chrome'da tekshirdik — klaviatura bilan ham, sichqoncha bilan ham bir xil handler ishladi.

Shuning uchun haqiqiy <button> ga click qo'ysangiz, Bobur (faqat klaviaturadan foydalanadi) ham, Malika (NVDA ekran o'quvchisi) ham uni ishlata oladi. Lekin div ga click qo'yish bunday emas: div ga Tab bilan borib bo'lmaydi va Enter uni "bosmaydi". Bu tugma nima uchun button bo'lishi kerak degan HTML qoidasining JavaScript tomoni.

Diqqat: Hodisa nomlari katta-kichik harfni farqlaydi. "Click" yoki "onclick" yozsangiz, xato chiqmaydi — brauzer shunchaki bunday nomli hodisani hech qachon yubormaydi va funksiya hech qachon ishlamaydi. Jim xato — eng ayyori.

5. Ko'p elementga tinglovchi

5.1 Sikl bilan

Menyuda uchta "Savatga" tugmasi bor. Har biriga tinglovchi kerak. querySelectorAll bilan hammasini topib, for...of bilan aylanamiz:

html
<article class="taom-karta">
  <h2 class="taom-nomi">Osh</h2>
  <button type="button">Savatga</button>
</article>
<article class="taom-karta">
  <h2 class="taom-nomi">Manti</h2>
  <button type="button">Savatga</button>
</article>
<article class="taom-karta">
  <h2 class="taom-nomi">Lag'mon</h2>
  <button type="button">Savatga</button>
</article>
<script>
  const tugmalar = document.querySelectorAll(".taom-karta button");

  for (const tugma of tugmalar) {
    tugma.addEventListener("click", () => {
      const karta = tugma.closest(".taom-karta");
      const nomi = karta.querySelector(".taom-nomi").textContent;
      console.log(`Savatga qo'shildi: ${nomi}`);
    });
  }
</script>

Manti kartasidagi tugmani bosing — konsolda "Savatga qo'shildi: Manti". Lag'mon tugmasini bosing — "Savatga qo'shildi: Lag'mon". Elementlarni tanlash darsidagi "tugmadan kartaga" qolipi endi haqiqiy bosish bilan ishlayapti.

Har funksiya o'z tugma sini qanday "eslab qoladi"? Sikl har aylanishda yangi tugma o'zgaruvchisini yaratadi (const — blok ichida, Scope darsidagi qoida). Har arrow funksiya o'zi yaratilgan aylanishdagi tugma ni ko'radi. Bu xususiyatning nomi — closure. Uni 10-qismda chuqur o'rganamiz, hozir bilish shart emas.

5.2 Bu usulning chegarasi

Ikki muammo bor:

  • Kartalar 3 ta emas, 300 ta bo'lsa — 300 ta tinglovchi.
  • Sahifa ochilgandan keyin qo'shilgan kartada tinglovchi yo'q. Sikl bir marta ishladi va yangi tugmani "ko'rmaydi".

Ikkinchisini sinab ko'ring: «Yangi taom» tugmasi kartani Element yaratish darsidagi append bilan qo'shadi.

html
<div id="menyu">
  <article class="taom-karta">
    <h2 class="taom-nomi">Osh</h2>
    <button type="button">Savatga</button>
  </article>
</div>
<button id="yangi" type="button">Yangi taom qo'shish</button>
<script>
  const menyu = document.getElementById("menyu");

  for (const tugma of menyu.querySelectorAll("button")) {
    tugma.addEventListener("click", () => {
      console.log("Savatga qo'shildi");
    });
  }

  document.getElementById("yangi").addEventListener("click", () => {
    const karta = document.createElement("article");
    karta.className = "taom-karta";
    karta.innerHTML = `<h2 class="taom-nomi">Manti</h2>
      <button type="button">Savatga</button>`;
    menyu.append(karta);
  });
</script>

Avval Osh kartasidagi «Savatga» ni bosing — konsolda "Savatga qo'shildi". Endi «Yangi taom qo'shish» ni bosing: Manti kartasi paydo bo'ladi. Uning «Savatga» tugmasini bosing — konsol jim.

CSS'da bunday muammo yo'q edi: yangi button sahifaga qo'shilsa, u darhol hamma stilni oladi (Tugmalar tizimi darsida aytilganidek). JavaScript tinglovchisi esa stil emas: u faqat qo'shilgan paytda mavjud bo'lgan elementlarga biriktiriladi.

Ikkala muammoning ham chiroyli yechimi bor — Event delegation. Uni hodisa qanday "tarqalishini" o'rgangach ko'ramiz. Hozircha sikl bilan ishlash to'liq to'g'ri, faqat chegarasini biling.

Tekshirib ko'ring: Yuqoridagi misolda yangi kartaning tugmasi ham ishlashi uchun eng oddiy (lekin unchalik chiroyli bo'lmagan) yo'l qaysi?

Javob

Kartani yaratgan zahoti uning tugmasiga ham tinglovchi qo'shish: karta.querySelector("button").addEventListener("click", ...). Ishlaydi, lekin endi bitta funksiya ikki joyda yoziladi. Delegation buni bitta tinglovchi bilan hal qiladi.

5.3 Tinglovchi elementning o'zida yashaydi

Oldingi bo'limdagi jim tugma bir muhim faktni ko'rsatdi. Tinglovchi CSS qoidasi kabi "butun sahifaga" yozilmaydi — u aynan bitta elementga biriktiriladi va o'sha element bilan yashaydi. O'tgan darslarda bu haqda bir necha marta "keyin ko'ramiz" degan edik. Mana hammasi bir joyda.

Avval nusxa bilan sinaymiz:

html
<p id="qator">
  <button type="button" id="asl">Osh</button>
</p>
<script>
  const qator = document.getElementById("qator");
  const asl = document.getElementById("asl");
  asl.addEventListener("click", () => {
    console.log("Osh savatga qo'shildi");
  });

  const nusxa = asl.cloneNode(true);
  nusxa.removeAttribute("id");
  nusxa.textContent = "Osh (nusxa)";
  qator.append(" ", nusxa);
</script>

«Osh» ni bosing — konsolda "Osh savatga qo'shildi". «Osh (nusxa)» ni bosing — konsol jim. cloneNode(true) teg, atribut va matnni nusxaladi, tinglovchini esa nusxalamadi (Element yaratish darsidagi va'da).

Element bilan boshqa ishlar qilinganda nima bo'ladi:

Element bilan nima bo'ldi Tinglovchi
append yoki before bilan boshqa joyga ko'chdi birga ko'chadi va ishlayveradi
cloneNode(true) bilan nusxalandi nusxaga o'tmaydi
Ota innerHTML bilan qayta yozildi yo'qoladi — ichidagi elementlar yangi
render() ro'yxatni qayta chizdi eski tugma bilan birga ketadi

Uchinchi qator — Kontentni o'zgartirish darsidagi innerHTML += tuzog'ining yana bir sababi. To'rtinchisi esa state → render darsida tugmaga tinglovchini aynan render() ichida, tugma yasalgan zahoti qo'shganimizning sababi: har chizishda yangi tugma — yangi tinglovchi.

Qoida: tinglovchini element yaratilgan joyda qo'shing. Yoki barcha elementlarning otasiga bitta tinglovchi qo'ying — bu Event delegation yo'li.

Tekshirib ko'ring: «Osh» tugmasini document.body.append(asl) bilan sahifa oxiriga ko'chirsak, uni bosganda konsolda xabar chiqadimi?

Javob

Ha. Ko'chirish elementni yo'q qilmaydi — o'sha element boshqa joyga o'tadi, tinglovchi esa unda qoladi. Nusxa yoki innerHTML esa yangi element yaratadi, yangi elementda tinglovchi yo'q.

6. removeEventListener — tinglovchini olib tashlash

6.1 Qachon kerak?

Ba'zan tinglovchi faqat bir muddat kerak. Masalan, Jasur aka aksiya qildi: "Ko'k choy bepul — faqat birinchi 3 mehmonga". Uchinchi bosishdan keyin tugma endi hech narsa qilmasligi kerak:

html
<button id="aksiya" type="button">Bepul choy olish</button>
<p id="qoldi">Qoldi: 3 ta</p>
<script>
  const tugma = document.getElementById("aksiya");
  const qoldi = document.getElementById("qoldi");
  let soni = 3;

  function choyBer() {
    soni--;
    qoldi.textContent = `Qoldi: ${soni} ta`;
    if (soni === 0) {
      tugma.removeEventListener("click", choyBer);
      qoldi.textContent = "Aksiya tugadi";
    }
  }

  tugma.addEventListener("click", choyBer);
</script>

Tugmani to'rt-besh marta bosing. Uchinchi bosishda "Aksiya tugadi" chiqadi, keyingi bosishlar hech narsani o'zgartirmaydi — tinglovchi yo'q.

removeEventListener ham ikki argument oladi: hodisa turi va funksiya. Brauzer tugmaning ro'yxatidan aynan shu funksiyani qidirib, o'chiradi.

6.2 Tuzoq: "bir xil ko'rinadigan" funksiya

Endi ko'p boshlovchi qiladigan xato. Funksiya joyida yozilgan va xuddi shunday funksiya bilan o'chirilmoqchi:

html
<button id="aksiya" type="button">Bepul choy olish</button>
<button id="toxtat" type="button">Aksiyani to'xtatish</button>
<script>
  const tugma = document.getElementById("aksiya");

  tugma.addEventListener("click", () => {
    console.log("Choy berildi");
  });

  document.getElementById("toxtat").addEventListener("click", () => {
    tugma.removeEventListener("click", () => {
      console.log("Choy berildi");
    });
    console.log("To'xtatildi (shekilli)");
  });
</script>

«Aksiyani to'xtatish» ni bosing — konsolda "To'xtatildi (shekilli)". Endi «Bepul choy olish» ni bosing: "Choy berildi" baribir chiqadi. Xato xabari ham yo'q.

Nega? Ikkala arrow funksiyaning matni bir xil, lekin ular ikki xil funksiya. Har () => {...} yozilganda yangi funksiya yaratiladi. Xuddi Havola semantikasi darsidagi obyektlar kabi: ikki bir xil obyekt {} ham bir-biriga teng emas, chunki ular ikki xil "quti". removeEventListener ro'yxatdan ikkinchi qutini qidirdi, topmadi va jim tugadi.

Tuzatish: olib tashlanadigan funksiyaga nom bering (yoki const ga saqlang) va ikkala joyda o'sha nomni ishlating — yuqoridagi choyBer kabi.

Maslahat: Tinglovchini olib tashlashning yana ikki qulay yo'li bor: "faqat bir marta ishlasin" opsiyasi va bir nechta tinglovchini birdaniga o'chiradigan AbortController. Ular Listener opsiyalari darsida.

Tekshirib ko'ring: tugma.removeEventListener("click", choyBer) chaqirildi, lekin choyBer hech qachon qo'shilmagan edi. Nima bo'ladi?

Javob

Hech narsa: xato ham chiqmaydi, hech narsa o'zgarmaydi. Brauzer ro'yxatdan choyBer ni qidiradi, topmaydi va jim tugaydi. Shuning uchun noto'g'ri removeEventListener ni topish qiyin — natijani doim tekshiring.

7. onclick: eski ikki yo'l

Eski kodlarda va internetdagi ko'p misollarda tinglovchi boshqacha yoziladi. Ularni tanishingiz kerak — lekin o'zingiz addEventListener yozasiz. Nega — hozir ko'rasiz.

7.1 onclick xususiyati

Har elementda onclick, oninput, onload kabi xususiyatlar bor. Ularga funksiya yozsangiz, u tinglovchi bo'ladi:

html
<button id="bron" type="button">Stol band qilish</button>
<script>
  const tugma = document.getElementById("bron");

  tugma.onclick = () => console.log("Birinchi funksiya");
  tugma.onclick = () => console.log("Ikkinchi funksiya");

  tugma.addEventListener("click", () => console.log("A"));
  tugma.addEventListener("click", () => console.log("B"));
</script>

Tugmani bir marta bosing. Konsolda uch qator chiqadi: "Ikkinchi funksiya", "A", "B". "Birinchi funksiya" yo'q.

Sabab: onclick — oddiy xususiyat, unda bitta qiymat turadi. Ikkinchi = birinchisini almashtirib yubordi — o'zgaruvchiga qayta yozish kabi. addEventListener esa ro'yxatga qo'shadi.

Bu katta loyihada xavfli. Sizning kodingiz onclick ga funksiya yozdi, keyin ulangan analitika kodi ham onclick ga yozdi — sizniki jimgina yo'qoldi. addEventListener bilan ikkalasi yonma-yon ishlaydi.

onclick xususiyatini o'chirish uchun null yoziladi: tugma.onclick = null.

7.2 onclick="..." HTML atributi

Eng eski usul — tinglovchini to'g'ridan-to'g'ri HTML'ga yozish:

html
<button type="button" onclick="console.log(this)">Menyu</button>

Bosing — konsolda tugmaning o'zi chiqadi: <button type="button" onclick="console.log(this)">Menyu</button>. Atribut ichidagi matn — JavaScript kodi. Brauzer uni funksiyaga aylantiradi va bosilganda chaqiradi. Ichidagi this — tugmaning o'zi (bu haqda pastda).

Bu usulni CSS yuklash samaradorligi darsida ko'rgan edingiz: onload="this.media='all'". O'qilishi endi aniq: link elementida load hodisasi bo'lganda (fayl yuklanganda) shu kodni bajar, this — link ning o'zi.

Nega yangi kodda bu usul yozilmaydi?

  1. HTML va JavaScript aralashadi. Kod satr ichida — muharrir uni tekshirmaydi, xatoni ko'rsatmaydi.
  2. Faqat global funksiyalarni ko'radi. onclick="savatgaQosh()" ishlashi uchun funksiya global bo'lishi kerak. Modullar ichidagi funksiyani u ko'rmaydi.
  3. Xavfsizlik siyosati uni to'sadi. «Bahor»da Content Security Policy bor: u sahifadagi inline skriptni taqiqlaydi — onclick="..." atributi ham shunga kiradi. Chrome bunday tugma bosilganda kodni bajarmaydi va konsolga xato yozadi: Executing inline event handler violates the following Content Security Policy directive — "inline hodisa handlerini bajarish xavfsizlik siyosatini buzadi".

Bu jonli oynada atribut ishladi, chunki darslar sayti misollar uchun inline kodga ruxsat bergan. Haqiqiy «Bahor» sahifasida esa u ishlamaydi.

Usul Nechta funksiya CSP bilan Tavsiya
addEventListener istalgancha ishlaydi asosiy
el.onclick = f bitta ishlaydi eski kodda
onclick="..." bitta to'siladi yozmang

8. Handler ichida this

8.1 Oddiy funksiyada — element

Obyekt metodlari darsida this ni ko'rgan edingiz: hamyon.korsat() chaqirilsa, ichidagi this — hamyon. Tinglovchida ham shunga o'xshash qoida bor: brauzer handlerni chaqirganda this ga tinglovchi biriktirilgan elementni qo'yadi.

html
<button id="osh" type="button">Osh</button>
<script>
  const tugma = document.getElementById("osh");

  tugma.addEventListener("click", function () {
    console.log("function:", this === tugma);
  });

  tugma.addEventListener("click", () => {
    console.log("arrow:", this === tugma, this === window);
  });
</script>

Bosing. Konsolda:

  • function: true — oddiy function da this — tugma.
  • arrow: false true — arrow'da this tugma emas, window.

8.2 Arrow funksiyada — tashqaridagi this

Arrow funksiyaning o'z this i yo'q — 08-qismda "arrow metod ichida this obyekt emas" degan edik. U this ni o'zi yozilgan joydan oladi. Bu skript sahifaning eng yuqori darajasida yozilgan, u yerda this — window.

Modul skriptda (type="module") esa u yerdagi this — undefined. To'liq qoidalar — this: to'rt qoida darsida.

8.3 Amalda nima yozish kerak?

Ko'p dasturchi handlerda this ga umuman tayanmaydi. Element allaqachon o'zgaruvchida turibdi (tugma) — undan foydalanish aniqroq. Sikl ichida ham tugma o'zgaruvchisi bor ekanini «Ko'p elementga tinglovchi» bo'limida ko'rdik.

Elementga ishonchli yo'l ham bor: brauzer har handlerga hodisa haqidagi ma'lumot obyektini beradi, unda currentTarget degan xususiyat bor — u ham shu element. Uni keyingi darsda o'rganamiz. Bu obyekt handlerning parametriga keladi: (event) => { ... }. event — inglizcha "hodisa"; nom ixtiyoriy, lekin MDN va ish joyidagi kodda event (yoki qisqa e) yoziladi — biz event ishlatamiz.

Qoida: this ni ko'rsangiz (eski kodda tez-tez uchraydi), avval funksiya oddiymi yoki arrow ekanini tekshiring. Arrow bo'lsa — this element emas.

Tekshirib ko'ring: Eski kodda tugma.addEventListener("click", () => this.classList.add("faol")) yozilgan va xato chiqyapti. Sababi nima?

Javob

Arrow funksiyada this tugma emas — oddiy skriptda window. window.classList yo'q (undefined), unga .add chaqirilganda TypeError: Cannot read properties of undefined (reading 'add') chiqadi. Tuzatish: tugma.classList.add("faol") yoki oddiy function.

9. Amaliy qoliplar

Oldingi darslarda bir necha joyda "buni JavaScript qiladi" degan edik. Endi qo'limizda hamma asbob bor.

9.1 Bosilgan/bosilmagan tugma: aria-pressed

Tugmalar tizimi darsida aria-pressed li tugma — "yoqilgan/o'chirilgan" holatli tugma — haqida aytgan edik: holatni almashtirish JavaScript ishi. «Bahor» menyusida "Faqat achchiq" filtri:

html
<style>
  [aria-pressed="true"] { background: #2f6b3a; color: #fff; }
  .yashirin { display: none; }
</style>
<button id="achchiq" type="button" aria-pressed="false">
  Faqat achchiq
</button>
<ul>
  <li class="taom-karta">Osh</li>
  <li class="taom-karta achchiq">Lag'mon</li>
  <li class="taom-karta">Manti</li>
</ul>
<script>
  const filtr = document.getElementById("achchiq");

  filtr.addEventListener("click", () => {
    const yoqildi = filtr.getAttribute("aria-pressed") === "true";
    filtr.setAttribute("aria-pressed", String(!yoqildi));

    for (const karta of document.querySelectorAll(".taom-karta")) {
      const yashir = !yoqildi && !karta.matches(".achchiq");
      karta.classList.toggle("yashirin", yashir);
    }
  });
</script>

Tugmani bosing: u yashil bo'ladi, ro'yxatda faqat Lag'mon qoladi. Yana bosing — hammasi qaytadi. Uchta bilim birga ishladi: atribut o'qish va yozish (Atributlar), classList.toggle ikkinchi argument bilan (classList), matches (Elementlarni tanlash).

Ko'rinish CSS'da [aria-pressed="true"] selektorida — JavaScript faqat atributni almashtiradi. Malika esa NVDA'da "Faqat achchiq, tugma, bosilgan" ni eshitadi: holat ko'zga ham, quloqqa ham bir xil yetadi.

9.2 aria-disabled — bosishni JavaScript to'xtatadi

Tugmalar tizimi darsida disabled va aria-disabled ni solishtirgan edik. aria-disabled="true" tugmani Tab tartibida qoldiradi va Malika sababni eshitadi. Lekin brauzer bosishni to'xtatmaydi — buni handler qiladi:

html
<button id="yubor" type="button" aria-disabled="true"
  aria-describedby="sabab">Bronni yuborish</button>
<p id="sabab">Avval sanani tanlang</p>
<script>
  const tugma = document.getElementById("yubor");

  tugma.addEventListener("click", () => {
    if (tugma.getAttribute("aria-disabled") === "true") {
      console.log("Hali mumkin emas: sana tanlanmagan");
      return;
    }
    console.log("Bron yuborildi");
  });
</script>

Bosing — konsolda "Hali mumkin emas: sana tanlanmagan". "Tahrirlab ko'rish" bilan atributni aria-disabled="false" qiling va yana bosing — "Bron yuborildi". Handler boshidagi tekshiruv — erta qaytish qolipi.

Tugma formada type="submit" bo'lsa, return yetmaydi — brauzer baribir formani yuboradi. Uni to'xtatish uchun "standart harakatni bekor qilish" kerak, buni keyingi darsda o'rganamiz.

9.3 dialog ni JavaScript bilan ochish

Modal oyna darsida «Bahor»ning "Tezkor bron" oynasi HTML'ning o'zi bilan — command="show-modal" bilan ochilgan edi. U darsda ikki va'da bor edi: JavaScript'dagi showModal() metodi va eski brauzerlar uchun bir qatorlik zaxira. Mana ikkalasi:

html
<button id="tezkor" type="button">Tezkor bron</button>
<dialog id="bron-oyna">
  <p>Stol band qilish: 10:00–22:00</p>
  <form method="dialog"><button>Yopish</button></form>
</dialog>
<script>
  const oyna = document.getElementById("bron-oyna");

  document.getElementById("tezkor").addEventListener("click", () => {
    oyna.showModal();
    console.log("Oyna ochiq:", oyna.open);
  });
</script>

Bosing — oyna modal bo'lib ochiladi, konsolda "Oyna ochiq: true". Oyna Esc yoki «Yopish» bilan yopiladi. Bu yerdagi showModal() — dialog elementining metodi, command="show-modal" bilan aynan bir xil ish qiladi: oynani hamma narsaning ustidagi qatlamga (top layer) chiqaradi, orqadagi sahifani esa bosib ham, Tab bilan borib ham bo'lmaydigan qiladi (inert).

Qaysi biri qachon? Yangi brauzerlarda HTML yo'li yetadi — skript yuklanmasa ham ishlaydi. Invoker commands'ni bilmaydigan eski brauzer uchun yuqoridagi tinglovchi zaxira bo'ladi. Faqat ikkalasini bitta tugmaga birga qo'ymang: yangi brauzerda tugma oynani ikki marta ochmoqchi bo'ladi.

10. DevTools: tinglovchilarni ko'rish

Tugma bosilganda hech narsa bo'lmasa, birinchi savol: "tinglovchi umuman bormi?". Chrome DevTools'da javob bor:

  1. Elements panelida elementni tanlang.
  2. O'ng tomondagi Event Listeners tabini oching.
  3. Ro'yxatda click va uning ostida funksiya nomi, fayl va qator raqami ko'rinadi. Ustiga bossangiz, kod ochiladi.

Konsolda esa tanlangan element uchun getEventListeners($0) yozish mumkin. U {click: Array(1)} kabi obyekt qaytaradi. Bu ham $0, $$ kabi DevTools yordamchisi (Elementlarni tanlash darsida ko'rgansiz) — kodingizda ishlamaydi.

js
// DevTools Console'da, Elements'da tugma tanlangan holda:
getEventListeners($0);

Ro'yxat bo'sh bo'lsa — tinglovchi qo'shilmagan: selektor xatomi, skript ishga tushdimi, hodisa nomi to'g'rimi — shu tartibda tekshiring.

11. Ko'p uchraydigan xatolar

11.1 Funksiyani chaqirib yuborish

html
<button id="osh" type="button">Osh</button>
<script>
  function savatgaQosh() {
    console.log("Savatga qo'shildi");
  }
  const tugma = document.getElementById("osh");
  tugma.addEventListener("click", savatgaQosh());
</script>

Konsolda:

text
Savatga qo'shildi

Tugma hali bosilmagan, lekin xabar chiqdi! Bossangiz esa — hech narsa. savatgaQosh() — qavs bilan, ya'ni funksiya shu zahoti chaqirildi. U hech narsa qaytarmadi (undefined), va addEventListener ga funksiya o'rniga undefined berildi. Brauzer undefined ni jim e'tiborsiz qoldiradi.

Tuzatish: qavssiz — addEventListener("click", savatgaQosh). Funksiyaga argument berish kerak bo'lsa — uni arrow ichida chaqiring: addEventListener("click", () => savatgaQosh("Osh")).

11.2 Element topilmagan

html
<button id="savatga" type="button">Savatga</button>
<script>
  const tugma = document.querySelector("#savat");
  tugma.addEventListener("click", () => console.log("Bosildi"));
</script>

Konsolda:

text
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

Tarjimasi: "null ning xususiyatlarini o'qib bo'lmaydi (addEventListener ni o'qishda)". Selektorda harf xato (#savat, #savatga emas) — querySelector null qaytardi. Xabar addEventListener ni ko'rsatadi, lekin xato undan oldingi qatorda. Ikkinchi sabab — skript defer siz <head> da, tugma hali yo'q (Brauzer muhiti). Tekshirish: console.log(tugma) — null chiqsa, selektorni va skript joyini tekshiring.

11.3 Hodisa nomida on yoki katta harf

addEventListener("onclick", f) va addEventListener("Click", f) — xato chiqmaydi, funksiya hech qachon chaqirilmaydi. on faqat xususiyat va atribut nomida (onclick), hodisa nomi — click.

11.4 Tinglovchini ikki marta qo'shish

Funksiya ichida addEventListener yozilgan va funksiya har bosishda chaqirilsa, tinglovchilar ko'payib boradi. Joyida yozilgan arrow funksiyalar har safar yangi — brauzer ularni takror deb hisoblamaydi. Belgisi: birinchi bosishda bitta xabar, keyingisida ikkita, keyin uchta. Tinglovchini faqat bir marta — sahifa ochilganda qo'shing.

12. Mashqlar

1-mashq (oson): Savat xabari

Kontentni o'zgartirish darsida aria-live hududga xabarni sahifa ochilishi bilan yozgan edik va "tugma bosilganda yozishni keyin qilamiz" degan edik. Endi qiling: «Savatga» tugmasi bosilganda #savat-xabar ga Osh savatga qo'shildi. Savatda: 1 ta. yozilsin. Har keyingi bosishda son bittaga oshsin: Savatda: 2 ta., Savatda: 3 ta. Matnni textContent bilan yozing va konsolga ham chiqaring.

Ishora: sanoq uchun funksiyadan tashqarida let soni = 0 («Funksiyani joyida yozish» bo'limidagi ko'k choy misoli).

html
<button type="button" id="savatga">Savatga</button>
<p id="savat-xabar" aria-live="polite"></p>
<script>
  // shu yerga yozing
</script>
Yechim
html
<button type="button" id="savatga">Savatga</button>
<p id="savat-xabar" aria-live="polite"></p>
<script>
  const tugma = document.getElementById("savatga");
  const xabar = document.getElementById("savat-xabar");
  let soni = 0;

  tugma.addEventListener("click", () => {
    soni++;
    xabar.textContent = `Osh savatga qo'shildi. Savatda: ${soni} ta.`;
    console.log(xabar.textContent);
  });
</script>

Ikki marta bosing — konsolda Osh savatga qo'shildi. Savatda: 1 ta., keyin … 2 ta. Hudud HTML'da oldindan turibdi, JavaScript faqat uning matnini almashtiradi — Element yaratish darsidagi qoida. Shuning uchun Malika NVDA bilan har yangi xabarni eshitadi.

2-mashq (o'rta): Odamlar soni

Bron formasida odamlar soni + va − tugmalari bilan o'zgarsin. Chegaralar: kamida 1, ko'pi bilan 20 (bron formasidagi odam maydoni qoidasi). Son #soni elementida ko'rinsin. Ikkala tugma uchun ikkita tinglovchi, umumiy let odam = 2 o'zgaruvchisi.

html
<button id="kam" type="button" aria-label="Kamaytirish">−</button>
<output id="soni">2</output>
<button id="kop" type="button" aria-label="Ko'paytirish">+</button>
<script>
  // shu yerga yozing
</script>
Yechim
html
<button id="kam" type="button" aria-label="Kamaytirish">−</button>
<output id="soni">2</output>
<button id="kop" type="button" aria-label="Ko'paytirish">+</button>
<script>
  const soni = document.getElementById("soni");
  let odam = 2;

  function chiz() {
    soni.textContent = odam;
  }

  document.getElementById("kam").addEventListener("click", () => {
    if (odam > 1) {
      odam--;
    }
    chiz();
  });

  document.getElementById("kop").addEventListener("click", () => {
    if (odam < 20) {
      odam++;
    }
    chiz();
  });
</script>

− ni ikki marta bosing — 1 da to'xtaydi. + ni ko'p bossangiz — 20 da to'xtaydi. Son odam o'zgaruvchisida turadi, ekran esa chiz() bilan undan chiziladi — state → render g'oyasi kichik shaklda.

3-mashq (qiyin): Bir martalik aksiya kuponi

Uch taomning har biri yonida «Kupon» tugmasi bor. Mehmon faqat bitta kupon ishlata oladi: birinchi bosilgan tugma uchun konsolga Kupon: Manti kabi xabar chiqsin, keyin hamma kupon tugmalaridan tinglovchi olib tashlansin. removeEventListener ishlashi uchun funksiyaga nom bering. Bosilgan tugmaning taom nomini closest bilan oling.

html
<ul>
  <li class="taom-karta">
    Osh <button type="button">Kupon</button>
  </li>
  <li class="taom-karta">
    Manti <button type="button">Kupon</button>
  </li>
  <li class="taom-karta">
    Lag'mon <button type="button">Kupon</button>
  </li>
</ul>
<script>
  // shu yerga yozing
</script>

Ishora: taom nomi — li ning birinchi matn tuguni. Uni karta.firstChild.textContent.trim() bilan olsa bo'ladi (DOM daraxti, trim). Bosilgan tugmani topish uchun handlerda oddiy function va this ishlating — shunda bitta nomli funksiya hamma tugmaga yaraydi.

Yechim
html
<ul>
  <li class="taom-karta">
    Osh <button type="button">Kupon</button>
  </li>
  <li class="taom-karta">
    Manti <button type="button">Kupon</button>
  </li>
  <li class="taom-karta">
    Lag'mon <button type="button">Kupon</button>
  </li>
</ul>
<script>
  const tugmalar = document.querySelectorAll(".taom-karta button");

  function kuponIshlat() {
    const karta = this.closest(".taom-karta");
    const nomi = karta.firstChild.textContent.trim();
    console.log(`Kupon: ${nomi}`);

    for (const tugma of tugmalar) {
      tugma.removeEventListener("click", kuponIshlat);
    }
  }

  for (const tugma of tugmalar) {
    tugma.addEventListener("click", kuponIshlat);
  }
</script>

Manti kuponini bosing — konsolda "Kupon: Manti". Keyin istalgan kupon tugmasini bosing — konsol jim. Biz buni Chrome'da tekshirdik.

Nega function va this? Bitta kuponIshlat funksiyasi uch tugmaga biriktirildi. Qaysi tugma bosilganini this aytadi — u tinglovchi biriktirilgan element. Arrow funksiya bilan bu ishlamasdi. Funksiyaning yagona nusxasi bo'lgani uchun removeEventListener uni har tugmadan topib o'chira oldi.

4-mashq: Portfolio qadami — telefon menyusi va mavzu tugmasi

Ikki kichik ish. Ikkalasi assets/js/asosiy.js da.

1. Telefon menyusi. Portfolio menyusi Navbar va mobil menyu darsidan beri popover: «Menyu» tugmasi panelni ochadi va yopadi, Esc ham ishlaydi — JavaScript'siz. Lekin bitta kamchilik bor. Telefonda menyudan "Aloqa" ni bosing, keyin brauzerning "Orqaga" tugmasini bosing. Brauzer oldingi sahifani xotiradan tiklaydi — menyu ochiq holda. Biz buni Chrome'da sinadik: qaytgan sahifada panel ochiq turdi.

Tuzating: menyudagi har havolaga click tinglovchisi qo'shing, u panelni yopsin. Popover'ni JavaScript'dan yopadigan metod — hidePopover() (Popover API darsidagi popovertargetaction="hide" ning JavaScript varianti). Kompyuterda menyu yopiq popover — unga hidePopover() hech narsa qilmaydi va xato bermaydi.

2. Mavzu tugmasi. classList va stillar darsida <html> ga data-tema qo'yadigan temaniQoy(tema) funksiyasini yozdingiz va "uni tugma bosilganda chaqiramiz" dedik. Endi ulaymiz:

  • header'ga, «Menyu» tugmasidan oldin <button type="button" id="tema-tugma" aria-pressed="false">Qorong'i rejim</button> qo'shing (menyusi bor 5 sahifada);
  • tugma bosilganda: tugma bosilmagan (aria-pressed="false") bo'lsa — temaniQoy("qorongi"), bosilgan bo'lsa — temaniQoy("yorug"); keyin aria-pressed ni teskarisiga almashtiring («aria-pressed» bo'limidagi qolip);
  • tugma faqat JavaScript ishlaganda ko'rinsin: skriptsiz u hech narsa qilmaydi. Atributlar va dataset darsida <html> ga data-js belgisi qo'yiladigan bo'lgan. O'shanda "tema tugmasini CSS shunga qarab ko'rsatadi" degan edik — endi shu qoidani komponentlar.css ga yozing.

404.html da menyu ham, tugma ham yo'q — kod xato bermasligi uchun elementni tekshiring (menyuniTop dagi erta qaytish yoki ?.).

Yechim

asosiy.js oxiriga:

js
// Telefon menyusi: havola bosilganda panel yopilsin
// ("Orqaga" bilan qaytganda ochiq qolmasin)
const menyu = document.getElementById("asosiy-menyu");

if (menyu !== null) {
  for (const havola of menyu.querySelectorAll("a")) {
    havola.addEventListener("click", () => {
      menyu.hidePopover();
    });
  }
}

// Mavzu tugmasi
const temaTugma = document.getElementById("tema-tugma");

temaTugma?.addEventListener("click", () => {
  const yoqilgan = temaTugma.getAttribute("aria-pressed") === "true";
  temaniQoy(yoqilgan ? "yorug" : "qorongi");
  temaTugma.setAttribute("aria-pressed", String(!yoqilgan));
});

komponentlar.css ga:

css
/* Mavzu tugmasi faqat JavaScript ishlaganda (data-js — asosiy.js) */
:root:not([data-js]) #tema-tugma {
  display: none;
}

temaniQoy — classList darsidagi funksiyangiz, unga tegmang. Tugma uni faqat chaqiradi: qaror ("qaysi tema?") — tugma holatidan, ish ("atributni qo'yish") — funksiyada.

Tekshiring:

  1. DevTools'da telefon o'lchami (360), «Menyu» → "Aloqa" → "Orqaga". Menyu yopiq bo'lishi kerak.
  2. «Qorong'i rejim» → sayt qorong'i, yana bosing → yorug'. Elements panelida <html data-tema="…"> va tugmaning aria-pressed i almashinayotganini ko'ring. Kompyuteringiz tizim darajasida qorong'i bo'lsa, birinchi bosish ko'zga deyarli bilinmaydi — sayt allaqachon qorong'i edi.
  3. 404.html ni oching — Console'da xato yo'q.
  4. DevTools → Settings → "Disable JavaScript" bilan sahifani yangilang: mavzu tugmasi yo'qoladi, menyu esa (popover) ishlashda davom etadi.

Hozircha tanlov boshqa sahifaga o'tganda yo'qoladi — saqlashni 11-qismda (localStorage) qo'shamiz. Tugmaning boshlang'ich holatini tizim sozlamasiga moslash va uch holatli tanlov (yorug'/qorong'i/tizim) — matchMedia va foydalanuvchi sozlamalari darsida. Tugma stili portfolio tugmalar tizimidan keladi: header'dagi button qoidasi uni avtomatik bezaydi. Sikl bilan har havolaga tinglovchi qo'yish hozircha to'g'ri; Event delegation darsida uni bitta tinglovchiga aylantiramiz.

npm run check, keyin commit: JS: menyu yopilishi va mavzu tugmasini qo'sh.

13. Real ishda

  • Har bir interaktiv sahifa shu qatordan boshlanadi: element → addEventListener → funksiya. Telegram Web'dagi "Yuborish", Click ilovasidagi "To'lash", marketpleysdagi "Savatga" — hammasining tagida tinglovchi bor.
  • Freymvorklarda (React va boshqalar — kursda alohida o'rganamiz) siz onClick={...} kabi yozasiz. Ko'rinishi HTML atributiga o'xshaydi, lekin ichida baribir addEventListener g'oyasi ishlaydi. Bugungi bilim ular ichini tushunishga yordam beradi.
  • Xotira sizishi. Tinglovchi olib tashlanmasa va ko'paysa, sahifa sekinlashadi — buni Xotira sizishlari darsida o'lchaymiz.
  • Intervyuda tez-tez so'raladi: "onclick va addEventListener farqi?" (bitta va ko'p funksiya), "removeEventListener nega ishlamadi?" (boshqa funksiya havolasi), "arrow funksiyada this?".

Xulosa

  • Hodisa — "shu narsa yuz berdi" xabari; tinglovchi — u yuz berganda brauzer chaqiradigan funksiya.
  • el.addEventListener("click", funksiya) — funksiya qavssiz beriladi, uni brauzer chaqiradi.
  • Bir elementga ko'p tinglovchi qo'shish mumkin, ular qo'shilgan tartibda ishlaydi; bir xil funksiya ikki marta qo'shilmaydi.
  • Tinglovchi bitta elementda yashaydi: ko'chirilsa — birga ketadi; nusxa va innerHTML bilan yasalgan yangi elementda u yo'q.
  • removeEventListener uchun aynan o'sha funksiya kerak — joyida yozilgan arrow'ni olib tashlab bo'lmaydi.
  • onclick xususiyati faqat bitta funksiya saqlaydi, onclick="..." atributini CSP to'sadi — yangi kodda addEventListener.
  • Oddiy function da this — element, arrow'da — tashqaridagi this.

Keyingi dars: Event obyekti va standart harakatlar — handlerga keladigan hodisa obyektini ochamiz va havola yoki forma yuborilishini qanday to'xtatishni ko'ramiz.

Manbalar

  • MDN: "EventTarget: addEventListener() method", "EventTarget: removeEventListener() method" — developer.mozilla.org
  • MDN: "Introduction to events" — developer.mozilla.org
  • HTML Living Standard: "Event handlers" (onclick xususiyati va atributi) — html.spec.whatwg.org
  • Chrome DevTools: "View and debug event listeners", "Console Utilities API reference" (getEventListeners) — developer.chrome.com
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript hodisalari: addEventListener asoslari, removeEventListener va this — IlmHamroh