Obyekt usullari bilan setTimeout dan foydalanishda ma’lum bir muammo yuzaga keladi: “this ni yo’qotish”.
To’satdan, this to’g’ri ishlashni to’xtatadi. Vaziyat yangi boshlanuvchilar uchun odatiy holdir, ammo tajribali dasturchilar bilan ham sodir bo’ladi.
“this” ni yo’qotish
Biz allaqachon bilamizki, JavaScript-da this ni yo’qotish oson. Biror usulni obyektdan alohida joyga o’tkazgandan so’ng – this yo’qoladi.
Bu setTimeout bilan qanday sodir bo’lishi mumkin:
let user = {
firstName: "John",
sayHi() {
alert(`Salom, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Salom, undefined!
Ko’rib turganimizdek, chiqishda “John” this.firstName emas, balki undefined ko’rsatilgan!
Buning sababi, setTimeout obyektdan alohida user.sayHi funktsiyasini olgan. Oxirgi satrni quyidagicha yozish mumkin:
let f = user.sayHi;
setTimeout(f, 1000); // yo'qolgan foydalanuvchi konteksti
Brauzer ichidagi setTimeout usuli biroz o’ziga xos: funktsiya chaqiruvi uchun this=window ni o’rnatadi (Node.js uchun this taymer obyekti bo’ladi, lekin bu yerda muhim emas). Shunday qilib this.firstName uchun u mavjud bo’lmagan window.firstName ni olishga harakat qiladi. Ko’rganimiz kabi boshqa shunga o’xshash holatlarda odatda this undefined bo’ladi.
Vazifa odatiy holdir – biz obyekt usulini boshqa joyda (bu yerda – rejalashtiruvchiga) chaqirishni xohlaymiz. Qanday qilib to’g’ri kontekstda chaqirilishiga ishonch hosil qilish kerak?
Yechim 1: o’ralgan funktsiya
Oddiy yechim – o’ralgan funktsiyasidan foydalanish:
let user = {
firstName: "John",
sayHi() {
alert(`Salom, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // Salom, John!
}, 1000);
Endi u ishlaydi, chunki u user ni tashqi leksik muhitdan oladi va keyin odatdagi usulni chaqiradi.
Xuddi shu, ammo qisqaroq:
setTimeout(() => user.sayHi(), 1000); // Salom, John!
Yaxshi ko’rinadi, lekin bizning kod tuzilishimizda biroz zaiflik paydo bo’ladi.
setTimeout ishga tushirilgunga qadar nima sodir bo’ladi (kechikish bir soniya!) o’zgaruvchan user boshqa qiymatga ega bo’ladimi? Keyin, to’satdan, u noto’g’ri obyektni chaqiradi!
let user = {
firstName: "John",
sayHi() {
alert(`Salom, ${this.firstName}!`);
}
};
setTimeout(() => user.sayHi(), 1000);
// ...1 soniya ichida
user = { sayHi() { alert("setTimeout-da boshqa foydalanuvchi!"); } };
// setTimeout-da boshqa foydalanuvchi?!?
Keyingi yechim bunday narsa bo’lmasligini kafolatlaydi.
Yechim 2: bind
Funksiyalar this ni o’rnatishga imkon beradigan o’rnatilgan bind usulini taqdim etadi.
Asosiy sintaksis:
// murakkabroq sintaksis biroz keyinroq bo'ladi
let boundFunc = func.bind(context);
func.bind(context) ning natijasi funktsiya sifatida chaqiriladigan va shaffof ravishda func sozlamasini this=context ga o’tkazadigan “ekzotik obyekt” funktsiyasiga o’xshash.
Boshqa so’zlar bilan aytganda, boundFunc chaqiruvi sobit this bilan func chaqiruviga o’xshaydi.
Masalan, bu erda funcUser chaqiruvni func ga this=user bilan amalga oshiradi:
let user = {
firstName: "John"
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(user);
funcUser(); // John
Bu yerda func.bind(user) func ning “bog’langan varianti” sifatida, sobit this=user bilan.
Barcha argumentlar asl func ga uzatiladi, masalan:
let user = {
firstName: "John"
};
function func(phrase) {
alert(phrase + ', ' + this.firstName);
}
// this ni user ga bog'lash
let funcUser = func.bind(user);
funcUser("Hello"); // Salom, John ("Salom" argumenti berilgan va this=user)
Endi obyekt usuli bilan sinab ko’raylik:
let user = {
firstName: "John",
sayHi() {
alert(`Salom, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user); // (*)
sayHi(); // Salom, John!
setTimeout(sayHi, 1000); // Salom, John!
(*) satrida biz user.sayHi usulini qo’llaymiz va uni user bilan bog’laymiz. sayHi – bu “bog’langan” funktsiya, uni yakka o’zi chaqirish yoki setTimeout ga o’tkazish mumkin – bu muhim emas, kontekst to’g’ri bo’ladi.
Bu yerda argumentlar “boricha” berilganligini ko’rishimiz mumkin, faqat this bind bilan o’rnatiladi:
let user = {
firstName: "John",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Salom"); // Salom, John ("Salom" say ga bog'landi)
say("Hayir"); // Hayir, John ("Hayir" say ga bog'landi)
bindAllAgar obyekt juda ko’p usullarga ega bo’lsa va biz uni faol ravishda uzatishni rejalashtirmoqchi bo’lsak, unda biz ularni barchasini tsiklda birlashtirib bog’lashimiz mumkin:
for (let key in user) {
if (typeof user[key] == 'function') {
user[key] = user[key].bind(user);
}
}
JavaScript kutubxonalari, shuningdek, qulay ommaviy biriktirish uchun funktsiyalarni taqdim etadi, masalan, _.bindAll(obj) lodash-da.
Xulosa
func.bind(context, ... args) usuli this kontekstni tuzatuvchi va berilgan bo’lsa, birinchi argumentlarni func funktsiyasining “bog’langan variantini” qaytaradi.
Odatda biz this ni biron bir joyga o’tkazib yuborishimiz uchun bind ni obyekt usulida tuzatish uchun qo’llaymiz. Masalan, setTimeout ga. Zamonaviy dasturlashda bog'lash uchun ko’proq sabablar bor, biz ularni keyinroq uchratamiz.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)