Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. Nega yo'qoladi?
- 2.1 Metod — oddiy funksiya qiymati
- 2.2 Nega JavaScript metodni "eslab qolmaydi"?
- 2.3 Callback sifatida uzatish
- 2.4 Tinglovchi sifatida uzatish
- 2.5 Tayyor metodlar ham
- 3. this ni tiklashning uch yo'li
- 3.1 Arrow o'rovchi
- 3.2 bind — this ni mahkamlash
- 3.3 Metod o'zi callback yaratsa — ichki arrow
- 3.4 Brauzerda: element kerak bo'lsa — event.currentTarget
- 3.5 Uchta yo'lni solishtiramiz
- 4. Ko'p uchraydigan xatolar
- 4.1 Uzatish o'rniga chaqirish
- 4.2 bind natijasini ishlatmaslik
- 4.3 bind bilan removeEventListener
- 4.4 Metodni arrow qilib "tuzatish"
- 5. Mashqlar
- 1-mashq (oson): Navbat chaqiruvchi
- 2-mashq (o'rta): Kassa — uch xil yo'l bilan
- 3-mashq (qiyin): Oshxona taymeri
- 4-mashq: Vazifalar qadami — fokusla nega shunday yozilgan?
- 6. Real ishda
- Xulosa
- Manbalar
JavaScript this yo'qolishi: metodni callback qilib uzatish va this ni tiklash
Qisqacha: Metodni qavssiz uzatsangiz — o'zgaruvchiga olsangiz,
setTimeoutyokiaddEventListenerga bersangiz — faqat funksiyaning o'zi ketadi, "nuqtadan oldingi obyekt" qolib ketadi. Keyin uni kim chaqirsa,thiso'shanga qarab bo'ladi:undefined,windowyoki tugma. Tiklashning asosiy yo'llari: arrow o'rovchi() => obj.metod(),obj.metod.bind(obj)va metod ichidagi callback'ni arrow qilish.
Bu darsda
- Metodni uzatganda
thisnega yo'qolishini tushuntira olasiz. thisyo'qoladigan uch tipik joyni — o'zgaruvchi, callback, tinglovchi — tanib olasiz.thisni arrow o'rovchi,bindva ichki arrow bilan tiklay olasiz va vaziyatga qarab tanlaysiz.- Brauzerning tayyor metodlari bilan ham shu muammo bo'lishini va "Illegal invocation" xatosini bilasiz.
vazifalardagifokuslafunksiyasi nega aynan shunday yozilganini tajribada ko'rasiz.
Oldin bilishingiz kerak: this: to'rt qoida, Callback va HOF, Closure, addEventListener asoslari, Event obyekti.
1. Nega bu kerak?
Otabek oshxonaga buyurtma berdi. Taom tayyor bo'lganda dastur stol raqamini aytishi kerak. Buyurtma — obyekt, ovozli chaqiruv — uning metodi. "Taom bir soniyadan keyin tayyor bo'ladi" degan holatni setTimeout bilan yasaymiz:
const buyurtma = {
stol: 5,
taom: "Osh",
chaqir() {
console.log(`${this.stol}-stol: ${this.taom} tayyor!`);
},
};
buyurtma.chaqir();
setTimeout(buyurtma.chaqir, 1000);Konsolda:
5-stol: Osh tayyor!
undefined-stol: undefined tayyor!Birinchi chaqiruv to'g'ri. Ikkinchisi — o'sha metod, bir soniyadan keyin — undefined bilan chiqdi. Xato xabari yo'q, dastur to'xtamadi. Oshxonada esa hech kim qaysi stolga osh olib borishni bilmaydi.
Bu — JavaScript'dagi eng ko'p uchraydigan xatolardan biri. Uning nomi ham bor: this ning yo'qolishi (losing this). O'tgan darsda this chaqiruv shakliga bog'liq ekanini o'rgandik. Bugun shu qoidaning eng og'riqli oqibatini ko'ramiz: metodni uzatganda chaqiruv shakli o'zgaradi. Keyin this ni qaytarishning uch yo'lini o'rganamiz.
2. Nega yo'qoladi?
2.1 Metod — oddiy funksiya qiymati
buyurtma.chaqir() — qavs bilan — metodni chaqiradi. buyurtma.chaqir — qavssiz — faqat funksiyaning o'zini oladi (Callback va HOF darsidagi qavs farqi). Olingan qiymat — oddiy funksiya. U qaysi obyektdan olinganini eslab qolmaydi.
Hayotdan o'xshatish: uy kaliti. Kalitni cho'ntagingizga solsangiz, unda "5-xonadon" deb yozilmagan. Kalit — faqat temir bo'lagi. Qaysi eshikni ochishini siz bilasiz, kalit esa bilmaydi. Metod ham shunday: uni obyektdan olib chiqsangiz, u "men buyurtma niki edim" demaydi.
Buni ikki chaqiruvda solishtiramiz. Ikkinchisi — o'sha funksiya, faqat nuqtasiz:
const buyurtma = {
stol: 5,
chaqir() {
return `${this?.stol}-stol`;
},
};
const chaqir = buyurtma.chaqir;
console.log(chaqir === buyurtma.chaqir); // true
console.log(buyurtma.chaqir()); // 5-stol
console.log(chaqir()); // undefined-stolchaqir === buyurtma.chaqir — true: bu aynan bitta funksiya. Funksiya o'zgarmadi — o'zgargani faqat chaqiruv qatori. chaqir() da nuqtadan oldin hech narsa yo'q. Demak 2-qoida — oddiy chaqiruv — ishlaydi: strict rejimda (modul ham doim strict) this — undefined.
this?.stol dagi ?. nima uchun? Usiz undefined.stol xato berardi. ?. esa xatoni to'xtatib, undefined qaytardi (Optional chaining). Biz uni faqat natijani ko'rsatish uchun qo'ydik.
Qadamma-qadam yuring. this qaysi qatorda "yo'qolishini" kuzating:
Tekshirib ko'ring:
const { chaqir } = buyurtma; chaqir();—thisnima bo'ladi?
Javob
undefined (modulda). Destructuring ham const chaqir = buyurtma.chaqir bilan bir xil: funksiyaning o'zini oladi. Keyingi chaqir() — nuqtasiz chaqiruv.
2.2 Nega JavaScript metodni "eslab qolmaydi"?
Boshida bu noqulay tuyulishi tabiiy: nega funksiya o'z obyektini eslamaydi? Javob this: to'rt qoida darsining boshida edi. U yerda bitta tanishtir funksiyasi ikki obyektga — otabek va rustam ga — metod bo'lgan edi. Funksiya ikkalasiniki ham. Agar u "egasini" eslab qolganida, bitta funksiyani ko'p obyektda ishlatib bo'lmasdi.
JavaScript'da funksiya — oddiy qiymat, xuddi son yoki satr kabi. Uni o'zgaruvchiga yozish, massivga solish, boshqa funksiyaga berish mumkin. Bu juda kuchli imkoniyat: butun callback va HOF g'oyasi shunga qurilgan. Narxi esa shu darsdagi muammo: funksiya qayerga borsa, this ni o'sha yerdagi chaqiruvchi tanlaydi.
Shuning uchun qoidani shunday eslab qoling: metod obyektga bog'lanmagan — u faqat chaqiruv paytida bog'lanadi.
Maslahat: Metodda
this.birorkutilmagandaundefinedbo'lsa, metodning birinchi qatoriga vaqtinchaconsole.log(this);qo'ying. Konsolda kim chiqsa — o'sha chaqirgan. Brauzerda to'xtash nuqtasi ham yordam beradi: DevTools'ning Scope panelida, Local bo'limidathisning qiymati ko'rinadi (DevTools bilan debug qilish).
2.3 Callback sifatida uzatish
setTimeout(buyurtma.chaqir, 1000) da ham xuddi shu bo'ldi. Qavssiz buyurtma.chaqir — funksiyaning o'zi. setTimeout uni eslab qoladi va bir soniyadan keyin o'zicha chaqiradi. Nuqta ham, buyurtma ham bu chaqiruvda yo'q.
Massiv metodlari ham callback'ni nuqtasiz chaqiradi (Callback imzosi). Kassa har chekni qo'shib bormoqchi:
"use strict";
const kassa = {
jami: 0,
qosh(summa) {
this.jami += summa;
},
};
[35000, 28000].forEach(kassa.qosh);TypeError: Cannot read properties of undefined (reading 'jami')Tarjimasi: "undefined ning xususiyatlarini o'qib bo'lmaydi (jami ni o'qishda)". forEach qosh ni qosh(35000, 0, massiv) shaklida chaqirdi — nuqtasiz. Strict rejimda bunday chaqiruvda this — undefined, undefined.jami esa xato.
Endi qiziq savol: nega forEach da TypeError chiqdi, setTimeout da esa jim undefined? Chunki har HOF callback'ni o'zicha chaqiradi va this ga o'z narsasini qo'yadi:
forEachhech narsa qo'ymaydi —thisundefined,this.jamiesa xato beradi.- Node'dagi
setTimeoutthisga o'zining taymer obyektini (Timeout) qo'yadi. Undastolyo'q —undefined, lekin xato yo'q. - Brauzerdagi
setTimeoutthisgawindowni qo'yadi. Natija o'sha: jimundefined.
Ishonmasangiz, tekshiring. Har obyektda constructor.name bor — obyektni yasagan funksiyaning nomi. Hozircha uni "bu qanday obyekt?" savoliga javob deb qarang; to'liq Konstruktor funksiya va new darsida o'rganamiz. Bu blokni Node'da ishga tushiring; saytdagi «Ishga tushir» tugmasi kodni brauzerda bajaradi va u yerda Window chiqadi — ro'yxatdagi uchinchi holat:
setTimeout(function () {
console.log(this.constructor.name); // Timeout
}, 10);Ko'rdingizmi: this — "yo'q" emas, balki begona obyekt. Uchala holatda ham bizning buyurtma emas. Xato chiqmasa — eng yomoni: dastur undefined-stol bilan jimgina ishlayveradi.
Yig'ib olamiz. Metodni qavssiz uzatganingizda, uni kim chaqiradi va this nima bo'ladi:
| Kim chaqiradi | this |
Natija |
|---|---|---|
Siz, nuqtasiz: chaqir() |
undefined |
TypeError |
forEach, map, filter |
undefined |
TypeError |
setTimeout (Node) |
Timeout obyekti |
jim undefined |
setTimeout (brauzer) |
window |
jim undefined |
addEventListener |
element | TypeError yoki jim xato |
Oxirgi ustundagi "jim" so'ziga e'tibor bering. Xato chiqqani yaxshi — siz uni darhol ko'rasiz. Jim xato esa haftalab sezilmay yurishi mumkin.
2.4 Tinglovchi sifatida uzatish
Brauzerda tinglovchi ham callback. Brauzer uni chaqirganda this ga tugmani qo'yadi (this: to'rt qoida darsidagi brauzer bo'limi). «Bahor» savatining metodini to'g'ridan-to'g'ri tinglovchi qilib beramiz:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 1rem; }
</style>
<button id="osh" type="button">Oshni savatga</button>
<script>
const savat = {
taomlar: [],
qosh() {
this.taomlar.push("Osh");
console.log(this.taomlar.length);
},
};
const tugma = document.getElementById("osh");
tugma.addEventListener("click", savat.qosh);
console.log("tayyor");
</script>Tugmani bosing. Konsolda Uncaught TypeError: Cannot read properties of undefined (reading 'push') chiqadi. Biz buni Chrome'da tekshirdik. this — tugma, tugmada esa taomlar yo'q: this.taomlar — undefined, undefined.push(...) — xato.
Tekshirib ko'ring: Savatda
this.taomlar.pusho'rnigasavat.taomlar.pushyozilganida, tugma ishlarmidi?
Javob
Ha. savat.taomlar — this ga bog'liq emas, u savat nomini to'g'ridan-to'g'ri o'qiydi. Lekin bunday metodni boshqa obyektga berib bo'lmaydi va obyekt nomi o'zgarsa — buziladi. To'g'ri yo'l — this ni saqlab, metodni to'g'ri uzatish. Uni «this ni tiklashning uch yo'li» bo'limida ko'ramiz.
2.5 Tayyor metodlar ham
Bu muammo faqat sizning obyektlaringizda emas. document.querySelector ham metod — u qaysi hujjat ichidan qidirishni this dan biladi. Uni ajratib olsangiz, brauzer o'zi xato beradi:
<h1>Bahor</h1>
<script>
const izla = document.querySelector;
console.log(izla("h1"));
</script>Konsolda:
Uncaught TypeError: Illegal invocationTarjimasi: "Noqonuniy chaqiruv". Brauzerning ichki metodlari this to'g'ri obyekt ekanini tekshiradi. izla("h1") da this — document emas, shuning uchun brauzer chaqiruvni rad etdi. Bu xato brauzer konsolida uchraydi va doim bitta narsani bildiradi: brauzer metodi o'z obyektidan ajratib chaqirilgan.
JavaScript'ning o'z metodlari ham shunday. Massivning push metodi qaysi massivga qo'shishni this dan biladi:
const taomlar = ["osh"];
const { push } = taomlar;
push("manti");TypeError: Cannot convert undefined or null to objectTarjimasi: "undefined yoki null ni obyektga aylantirib bo'lmaydi". push ichida this — undefined, push esa unga element qo'shmoqchi bo'ldi. Xabarda this so'zi yo'q, shuning uchun bu xatoni tanish qiyin. Qoida: metodni ajratib olgan qatoringizdan keyin tushunarsiz TypeError chiqsa — avval this ni gumon qiling.
3. this ni tiklashning uch yo'li
3.1 Arrow o'rovchi
Eng sodda va eng ko'p ishlatiladigan yo'l — metodni uzatmaslik. Uning o'rniga o'rovchi (wrapper) funksiya uzatiladi, u esa metodni nuqta bilan chaqiradi:
const buyurtma = {
stol: 5,
taom: "Osh",
chaqir() {
console.log(`${this.stol}-stol: ${this.taom} tayyor!`);
},
};
setTimeout(() => buyurtma.chaqir(), 1000);Konsolda:
5-stol: Osh tayyor!setTimeout endi arrow'ni chaqiradi. Arrow ichida esa buyurtma.chaqir() — nuqta bilan, 1-qoida ishlaydi. O'rovchining yana bir foydasi — argument berish oson: forEach((summa) => kassa.qosh(summa)).
O'rovchi oddiy function ham bo'lishi mumkin: setTimeout(function () { buyurtma.chaqir(); }, 1000). U ham ishlaydi, chunki ichida baribir nuqta bilan chaqiruv. Arrow shunchaki qisqaroq — shuning uchun zamonaviy kodda deyarli doim arrow yoziladi.
O'rovchi buyurtma nomini closure orqali eslab qoladi. Demak u chaqiruv paytida o'zgaruvchida qaysi obyekt turgan bo'lsa, o'shaning metodini chaqiradi. Buni «Uchta yo'lni solishtiramiz» bo'limida bind bilan solishtiramiz.
3.2 bind — this ni mahkamlash
Har funksiyada bind metodi bor. f.bind(obyekt) funksiyani chaqirmaydi. U yangi funksiya qaytaradi, uning this i esa doim obyekt bo'ladi — kim, qanday chaqirmasin:
const buyurtma = {
stol: 5,
chaqir() {
console.log(`${this.stol}-stol tayyor`);
},
};
const boglangan = buyurtma.chaqir.bind(buyurtma);
setTimeout(boglangan, 100);
boglangan.call({ stol: 99 });Konsolda:
5-stol tayyor
5-stol tayyorKalit o'xshatishiga qaytsak: bind — kalitga "5-xonadon" deb yozilgan birka osish. Endi kalit kimning qo'liga tushsa ham, qaysi eshikniki ekani ma'lum.
Oxirgi qatorga qarang. call — o'tgan darsdagi 3-qoida: this ni qo'lda berish. Oddiy funksiyada u this ni { stol: 99 } ga almashtirardi. Lekin bog'langan funksiyada call ham birkani o'zgartira olmadi — yana 5-stol. bind ning qo'shimcha imkoniyatlari — argumentlarni oldindan berish — call, apply, bind darsida.
3.3 Metod o'zi callback yaratsa — ichki arrow
Ba'zan taymerni metodning o'zi qo'yadi. Masalan, buyurtma.kutish() — "oshxonaga yubor, tayyor bo'lganda chaqir". Bu holda o'rovchi ham, bind ham kerak emas — o'tgan darsdagi qoida yetadi: tashqi — metod, ichki — arrow.
const buyurtma = {
stol: 5,
chaqir() {
console.log(`${this.stol}-stol tayyor`);
},
kutish() {
console.log("Oshxonaga yuborildi");
setTimeout(() => this.chaqir(), 100);
},
};
buyurtma.kutish();Konsolda:
Oshxonaga yuborildi
5-stol tayyorkutish nuqta bilan chaqirildi — uning this i buyurtma. Arrow'ning o'z this i yo'q, u kutish nikini oladi. Ichida function () { this.chaqir(); } yozilganida esa Node'da this — Timeout obyekti bo'lardi va TypeError: this.chaqir is not a function chiqardi.
Tekshirib ko'ring:
kutishning o'zi arrow bo'lsa —kutish: () => { setTimeout(() => this.chaqir(), 100); }— ishlaydimi?
Javob
Yo'q. Endi ikkala funksiya ham arrow, ikkalasining ham o'z this i yo'q. Ichki arrow this ni kutish dan, kutish esa modulning yuqori darajasidan oladi — u yerda undefined. Natija: TypeError: Cannot read properties of undefined (reading 'chaqir'). Ichki arrow faqat tashqi funksiya oddiy metod bo'lgandagina yordam beradi.
Maslahat: Klasslarda
thisni oldindan mahkamlashning yana bir usuli bor — metodni klass maydoni sifatida arrow bilan yozish. Uni Class maydonlari darsida ko'ramiz — hozir bilish shart emas.
3.4 Brauzerda: element kerak bo'lsa — event.currentTarget
Tinglovchida ko'pincha obyekt emas, element kerak bo'ladi. U holda this ni umuman tiklamang — brauzer elementni event.currentTarget da beradi (Event obyekti). O'tgan darsda vazifalar uchun shu qarorni qabul qildik.
Bitta tuzoq bor. event.currentTarget faqat tinglovchi ishlayotgan paytda to'ldirilgan. Tinglovchi tugagach, brauzer uni null qiladi. Shuning uchun tinglovchi ichida setTimeout qo'ysangiz, elementni oldindan o'zgaruvchiga oling:
tugma.addEventListener("click", (event) => {
const bosilgan = event.currentTarget;
setTimeout(() => {
bosilgan.disabled = false;
}, 1000);
});setTimeout ichida event.currentTarget.disabled deb yozilganida, bir soniyadan keyin event.currentTarget allaqachon null bo'lardi va TypeError chiqardi. bosilgan o'zgaruvchisi esa elementni closure orqali ushlab turadi. Bu — this yo'qolishining "qarindoshi": qiymat noto'g'ri paytda o'qilsa, u boshqa narsa bo'lib chiqadi.
3.5 Uchta yo'lni solishtiramiz
Endi «Tinglovchi sifatida uzatish» bo'limidagi savatni tuzatamiz — ikki usulda. Uchinchi tugma esa eski, buzilgan variant — farqni ko'rish uchun:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 1rem; margin: 0.25rem; }
</style>
<button id="arrow" type="button">Osh (arrow)</button>
<button id="bind" type="button">Manti (bind)</button>
<button id="buzuq" type="button">Lag'mon (buzilgan)</button>
<script>
const savat = {
taomlar: [],
qosh(taom) {
this.taomlar.push(taom);
console.log(this.taomlar.join(", "));
},
};
const qoshBoglangan = savat.qosh.bind(savat, "Manti");
document.getElementById("arrow")
.addEventListener("click", () => savat.qosh("Osh"));
document.getElementById("bind")
.addEventListener("click", qoshBoglangan);
document.getElementById("buzuq")
.addEventListener("click", savat.qosh);
console.log("tayyor");
</script>Avval «Osh», keyin «Manti» ni bosing. Konsolda Osh, keyin Osh, Manti chiqadi — ikkalasi bitta savat ga qo'shdi. «Lag'mon» esa TypeError beradi. Biz buni Chrome'da tekshirdik.
bind(savat, "Manti") dagi ikkinchi argument — oldindan berilgan argument: qoshBoglangan chaqirilganda u qosh("Manti", ...) bo'ladi. Brauzer o'zi beradigan hodisa obyekti esa ikkinchi o'ringa tushadi va qosh uni e'tiborsiz qoldiradi.
Uch yo'l qayerda farq qiladi:
| Vaziyat | Yechim |
|---|---|
| Bir martalik callback, argument kerak | Arrow o'rovchi: () => obj.m(x) |
Funksiya keyin ham kerak (masalan, removeEventListener uchun) |
const f = obj.m.bind(obj) — havolani saqlang |
| Metod o'zi callback yaratadi | Ichki arrow: () => this.m() |
| Tinglovchiga element kerak | event.currentTarget |
O'rovchi va bind orasida yana bitta nozik farq bor. O'rovchi o'zgaruvchini chaqiruv paytida o'qiydi, bind esa obyektni bog'lash paytida mahkamlaydi:
let navbatdagi = {
stol: 5,
chaqir() {
console.log(`${this.stol}-stol tayyor`);
},
};
setTimeout(() => navbatdagi.chaqir(), 100);
setTimeout(navbatdagi.chaqir.bind(navbatdagi), 100);
navbatdagi = { stol: 7, chaqir: navbatdagi.chaqir };Konsolda:
7-stol tayyor
5-stol tayyorTaymerlar qo'yilgach, navbatdagi boshqa buyurtmaga almashdi. O'rovchi bir soniyadan keyin o'zgaruvchiga qaradi va yangisini (7-stol) topdi. bind esa bog'langan paytdagi obyektni (5-stol) ushlab qoldi. Ko'pincha bu farq sezilmaydi, lekin let o'zgaruvchi qayta yozilsa — esda tuting.
Tekshirib ko'ring: Nega
binddan keyinboglangan.call({ stol: 99 })ham5-stolchiqardi?
Javob
bind qaytargan funksiyaning this i bog'lash paytida mahkamlangan. call "aniq bog'lanish" bo'lsa ham, uni o'zgartira olmaydi — this allaqachon "qotgan". Bu birkani almashtirib bo'lmaydigan kalit. Undan faqat new kuchliroq — buni call, apply, bind darsida ko'ramiz.
4. Ko'p uchraydigan xatolar
4.1 Uzatish o'rniga chaqirish
const buyurtma = {
stol: 5,
chaqir() {
console.log(`${this.stol}-stol tayyor`);
},
};
setTimeout(buyurtma.chaqir(), 1000);TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received undefinedTarjimasi: "callback argumenti funksiya bo'lishi kerak. undefined keldi".
Avval 5-stol tayyor darhol chiqadi, keyin Node xato beradi. Nega? Qavs bilan buyurtma.chaqir() darhol bajarildi va undefined qaytardi. setTimeout ga funksiya emas, undefined bordi.
Brauzerda xato chiqmaydi — taymer shunchaki hech narsa qilmaydi. Bu yanada chalg'ituvchi. Kimdir this ni tuzatmoqchi bo'lib qavs qo'shadi — bu boshqa xato. Tuzatish: setTimeout(() => buyurtma.chaqir(), 1000).
4.2 bind natijasini ishlatmaslik
buyurtma.chaqir.bind(buyurtma); deb yozib, keyin setTimeout(buyurtma.chaqir, 1000) — ishlamaydi. bind asl metodni o'zgartirmaydi, u yangi funksiya qaytaradi. Tuzatish: natijani o'zgaruvchiga yozing yoki to'g'ridan-to'g'ri bering: setTimeout(buyurtma.chaqir.bind(buyurtma), 1000).
4.3 bind bilan removeEventListener
tugma.addEventListener("click", savat.qosh.bind(savat));
tugma.removeEventListener("click", savat.qosh.bind(savat));Tinglovchi olib tashlanmaydi. Har bind chaqiruvi yangi funksiya yasaydi — ikkinchi qatordagi funksiya birinchisidan boshqa:
const savat = { qosh() {} };
const a = savat.qosh.bind(savat);
const b = savat.qosh.bind(savat);
console.log(a === b); // falseremoveEventListener aynan o'sha funksiyani kutadi (addEventListener asoslari). Tuzatish: bog'langan funksiyani bir marta yasab, o'zgaruvchida saqlang va ikkala joyda shu o'zgaruvchini bering. Arrow o'rovchi ham xuddi shunday: har yozilganda yangi funksiya.
4.4 Metodni arrow qilib "tuzatish"
this yo'qolganini ko'rgan boshlovchi ba'zan metodning o'zini arrow qiladi: chaqir: () => { ... }. Bu yanada yomon: endi buyurtma.chaqir() ham ishlamaydi, chunki arrow'ning o'z this i yo'q (this: to'rt qoida). Arrow uzatiladigan o'rovchi yoki ichki callback bo'lishi kerak, metodning o'zi emas.
5. Mashqlar
1-mashq (oson): Navbat chaqiruvchi
Kod undefined-raqam chiqaryapti. Uni arrow o'rovchi bilan tuzating — natija 12-raqam bo'lsin.
const navbat = {
raqam: 12,
chaqir() {
console.log(`${this.raqam}-raqam`);
},
};
setTimeout(navbat.chaqir, 500);Yechim
const navbat = {
raqam: 12,
chaqir() {
console.log(`${this.raqam}-raqam`);
},
};
setTimeout(() => navbat.chaqir(), 500);Konsolda:
12-raqamsetTimeout endi o'rovchini chaqiradi, o'rovchi esa navbat.chaqir() ni nuqta bilan. bind ham to'g'ri javob: setTimeout(navbat.chaqir.bind(navbat), 500).
2-mashq (o'rta): Kassa — uch xil yo'l bilan
«Callback sifatida uzatish» bo'limidagi kassani uch xil tuzating: arrow o'rovchi, bind va forEach ning thisArg argumenti (this: to'rt qoida). Har yo'ldan keyin kassa.jami ni chiqaring.
Ishora: har yo'ldan oldin kassa.jami = 0; qiling.
Yechim
const kassa = {
jami: 0,
qosh(summa) {
this.jami += summa;
},
};
const cheklar = [35000, 28000];
cheklar.forEach((summa) => kassa.qosh(summa));
console.log(kassa.jami); // 63000
kassa.jami = 0;
cheklar.forEach(kassa.qosh.bind(kassa));
console.log(kassa.jami); // 63000
kassa.jami = 0;
cheklar.forEach(kassa.qosh, kassa);
console.log(kassa.jami); // 63000Uchalasi ishlaydi. thisArg — eng kam ishlatiladigani: uni o'qigan odam ikkinchi argument nima ekanini eslashi kerak. Arrow o'rovchi esa hammasidan aniq: chaqiruv ko'z oldingizda.
3-mashq (qiyin): Oshxona taymeri
taymer obyekti uch soniyani sanashi kerak: 3, 2, 1, keyin Osh tayyor!. Kod hozir NaN chiqarib, to'xtamayapti. Xatoni toping va ikki xil tuzating: ichki arrow bilan va bind bilan.
const taymer = {
qoldi: 3,
qadam() {
if (this.qoldi === 0) {
console.log("Osh tayyor!");
return;
}
console.log(this.qoldi);
this.qoldi--;
setTimeout(this.qadam, 100);
},
};
taymer.qadam();Ishora: birinchi 3 to'g'ri chiqadi. Ikkinchi chaqiruvni kim qilyapti?
Yechim
Birinchi taymer.qadam() — nuqta bilan, this — taymer. Keyingilarini esa setTimeout chaqiradi va Node'da this — Timeout obyekti. Unda qoldi yo'q: undefined === 0 — false, undefined-- — NaN, va taymer to'xtamaydi.
Ichki arrow bilan:
const taymer = {
qoldi: 3,
qadam() {
if (this.qoldi === 0) {
console.log("Osh tayyor!");
return;
}
console.log(this.qoldi);
this.qoldi--;
setTimeout(() => this.qadam(), 100);
},
};
taymer.qadam();Konsolda:
3
2
1
Osh tayyor!bind bilan — o'zgargan qator setTimeout(this.qadam.bind(this), 100);:
const taymer = {
qoldi: 3,
qadam() {
if (this.qoldi === 0) {
console.log("Osh tayyor!");
return;
}
console.log(this.qoldi);
this.qoldi--;
setTimeout(this.qadam.bind(this), 100);
},
};
taymer.qadam();Konsolda:
3
2
1
Osh tayyor!Ikkalasi ham to'g'ri. Ichki arrow qisqaroq va zamonaviy kodda ko'proq uchraydi. bind esa har qadamda yangi funksiya yasaydi — bu yerda bu muammo emas.
4-mashq: Vazifalar qadami — fokusla nega shunday yozilgan?
refactor/this-va-bind branch'ida davom etamiz. vazifalar.js dagi fokusla funksiyasini oching:
function fokusla(id, amal, zaxiraId) {
const top = (i) =>
royxat.querySelector(`[data-id="${i}"] [data-amal="${amal}"]`);
const nishon = top(id) ?? top(zaxiraId) ?? sarlavha;
nishon.focus();
}top — arrow o'rovchi: u ichida royxat.querySelector(...) ni nuqta bilan chaqiradi. Kimdir uni "qisqartirmoqchi" bo'ldi:
function fokusla(id, amal, zaxiraId) {
const izla = royxat.querySelector;
const top = (i) =>
izla(`[data-id="${i}"] [data-amal="${amal}"]`);
const nishon = top(id) ?? top(zaxiraId) ?? sarlavha;
nishon.focus();
}1. Shu variantni vaqtincha yozib, saytda istalgan vazifaning katagini bosing. Nima bo'ladi — ekranda va konsolda?
2. izla ni bind bilan tuzating va xulq avvalgidek ekanini tekshiring.
3. Qaysi variantni qoldirasiz? Tanlovingizni kod izohida tushuntiring.
Yechim
1. Katak belgilanadi va hisoblagich o'zgaradi — vazifaniAlmashtir ishlab ulgurdi. Lekin fokus katakda qolmaydi, body ga tushadi. Konsolda:
Uncaught TypeError: Illegal invocationizla(...) — nuqtasiz chaqiruv. querySelector qaysi element ichidan qidirishni this dan biladi, this esa royxat emas. Brauzer «Tayyor metodlar ham» bo'limidagidek chaqiruvni rad etdi va focus() gacha yetib bormadi. Malika NVDA bilan ishlaganda fokus yo'qolishi — u ro'yxatda qayerda turganini bilmay qoladi.
2. bind bilan:
const izla = royxat.querySelector.bind(royxat);Endi izla ning this i doim royxat. Belgilash, o'chirish, tahrirlash — fokus avvalgidek to'g'ri joyga tushadi. Biz ikkala variantni ham Chrome'da 18 holatli ssenariy bilan sinadik: bind varianti asl kod bilan bir xil ishladi, "qisqartirilgan" variant esa birinchi bosishdayoq xato berdi.
3. Asl arrow o'rovchini qoldiramiz. U bind dan uzun emas, this haqida o'ylashni talab qilmaydi va selektorni ham o'zi yasaydi. Kelajakdagi "qisqartiruvchi" uchun izoh qo'shing:
function fokusla(id, amal, zaxiraId) {
// querySelector — royxat'ning metodi: uni nuqta bilan chaqiramiz.
// Ajratib olinsa (const izla = royxat.querySelector), this
// yo'qoladi va brauzer "Illegal invocation" xatosini beradi.
const top = (i) =>
royxat.querySelector(`[data-id="${i}"] [data-amal="${amal}"]`);
const nishon = top(id) ?? top(zaxiraId) ?? sarlavha;
nishon.focus();
}git add assets/js/vazifalar.js
git commit -m "Ro'yxat: fokusla dagi arrow o'rovchini izohla"Xulq o'zgarmadi — faqat izoh qo'shildi. Branch keyingi darsda yakunlanadi.
6. Real ishda
- React (eski). Klass komponentlarida konstruktor ichida
this.handleClick = this.handleClick.bind(this)qatori deyarli har faylda bo'lgan — aynan shu darsdagi muammo tufayli. React — sahifa interfeysini yasaydigan kutubxona; uni 17-qismda o'rganamiz, hozir bilish shart emas. Zamonaviy React funksiyalar bilan yoziladi, lekin eski loyihalarda bu qatorni albatta ko'rasiz. - Node. Hodisa chiqaruvchi obyektlarga (EventEmitter) metod uzatganda
this— chiqaruvchining o'zi bo'ladi. Yechim o'sha: arrow o'rovchi. - Taymer va animatsiya.
setTimeout, takrorlanuvchisetInterval(Taymerlar, 11-qism) va animatsiya uchunrequestAnimationFrame— hammasi callback'ni o'zicha chaqiradi. Obyekt metodini berishdan oldin doim o'ylang: "bu yerdathiskerakmi?" - Intervyu. "Nega
setTimeout(obj.method, 0)ishlamaydi va qanday tuzatiladi?" — klassik savol. Javob: metod obyektdan ajraldi,thisyo'qoldi; arrow o'rovchi yokibind.
Xulosa
- Qavssiz
obj.metod— oddiy funksiya qiymati. U qaysi obyektdan olinganini eslamaydi. - Metodni o'zgaruvchiga olsangiz, callback yoki tinglovchi qilib bersangiz —
thisni endi chaqiruvchi tanlaydi:undefined,Timeout,windowyoki tugma. - Xato ba'zan chiqmaydi (
undefined-stol) — bu eng xavfli holat. Brauzer metodlari esa "Illegal invocation" beradi. - Tiklash: arrow o'rovchi
() => obj.m(),obj.m.bind(obj), metod ichida — ichki arrow. Element kerak bo'lsa —event.currentTarget. bindhar safar yangi funksiya yasaydi:removeEventListeneruchun uni o'zgaruvchida saqlang.
Keyingi dars: call, apply, bind — this ni qo'lda beradigan uch metodni to'liq o'rganamiz: argumentlarni oldindan berish va boshqa obyektning metodini "qarzga olish".
Manbalar
- MDN: "this" — "Callbacks" va "Bound methods in classes" bo'limlari — developer.mozilla.org
- MDN: "Function.prototype.bind()"
- WHATWG HTML Standard: "Timers" —
setTimeoutcallback'i qanday chaqiriladi — html.spec.whatwg.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!