25 август 2025

Funktsiyani bog'lash

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)
Qulaylik usuli: bindAll

Agar 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.

Vazifalar

Chiqish qanday bo’ladi?

function f() {
  alert(this); // ?
}

let user = {
  g: f.bind(null),
};

user.g();

Javob: null.

function f() {
  alert(this); // null
}

let user = {
  g: f.bind(null),
};

user.g();

Bog’langan funktsiya konteksti qat’iy belgilangan. Buni yanada o’zgartirishning iloji yo’q.

Shunday qilib, biz user.g() ni ishga tushirganimizda ham asl funktsiya this=null bilan chaqiriladi.

Qo’shimcha bog’lash orqali this ni o’zgartira olamizmi?

Chiqish qanday bo’ladi?

function f() {
  alert(this.name);
}

f = f.bind({ name: "John" }).bind({ name: "Ann" });

f();

Javob: John.

function f() {
  alert(this.name);
}

f = f.bind({ name: "John" }).bind({ name: "Pete" });

f(); // John

f.bind(...) tomonidan qaytarilgan ekzotik bog’langan funktsiya obyekti kontekstni eslaydi (va argumentlar, agar taqdim etilsa) faqat yaratilish vaqtida.

Funksiyani qayta bog’lab bo’lmaydi.

muhimlik: 5

Funksiya xususiyatida qiymat mavjud. U bind dan keyin o’zgaradimi? Javobingizni asoslang.

function sayHi() {
  alert( this.name );
}
sayHi.test = 5;

let bound = sayHi.bind({
  name: "John"
});

alert( bound.test ); // chiqishi qanday bo'ladi? nega?

Javob: undefined.

bind natijasi boshqa obyekt. U test xususiyatiga ega emas.

muhimlik: 5

Quyidagi koddagi askPassword() ga chaqiruv parolni tekshirishi kerak va keyin javobga qarab user.loginOk/loginFail chaqirishi kerak.

Ammo bu xatoga olib keladi. Nima uchun?

Hammasi to’g’ri ishlashni boshlashi uchun ajratilgan satrni to’g’rilab qo’ying (boshqa satrlarni o’zgartirish kerak emas).

function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  loginOk() {
    alert(`${this.name} logged in`);
  },

  loginFail() {
    alert(`${this.name} failed to log in`);
  },

};

askPassword(user.loginOk, user.loginFail);

Xato yuz berdi, chunki ask obyektsiz loginOk/loginFail funktsiyalarini oladi.

Ularni chaqirganda, ular tabiiy ravishda this=undefined deb taxmin qilishadi.

Keling, kontekstni bog’laymiz:

function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  loginOk() {
    alert(`${this.name} logged in`);
  },

  loginFail() {
    alert(`${this.name} failed to log in`);
  },

};

askPassword(user.loginOk.bind(user), user.loginFail.bind(user));

Endi u ishlaydi.

Muqobil yechim bo’lishi mumkin:

//...
askPassword(
  () => user.loginOk(),
  () => user.loginFail()
);

Odatda bu ham ishlaydi, lekin user so’rash va ishga tushirish vaqtlari orasida yozilishi mumkin bo’lgan murakkab vaziyatlarda ishlamay qolishi mumkin () => user.loginOk ().

It’s a bit less reliable though in more complex situations where user variable might change after askPassword is called, but before the visitor answers and calls () => user.loginOk().

Vazifa this yo'qotishni so'rang ning biroz murakkab variantidir.

user obyekti o’zgartirildi. Endi ikkita funktsiya loginOk/loginFail o’rniga, unda bitta user.login(true/false) funktsiyasi mavjud.

Quyidagi kodda askPassword-ni user.login(true) ok va user.login(false) fail deb chaqirishi uchun nima qilish kerak?

function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  login(result) {
    alert( this.name + (result ? ' logged in' : ' failed to log in') );
  }
};

askPassword(?, ?); // ?

Siz faqat ajratilgan qismni o’zgartirishiz kerak.

  1. Yoki o’rash funktsiyasidan foydalaning, qisqacha bo’lishi uchun o’q:

    askPassword(
      () => user.login(true),
      () => user.login(false)
    );

    Endi u tashqi o’zgaruvchanlardan user oladi va odatdagi usulda ishlaydi.

  2. Yoki user ni kontekst sifatida ishlatadigan va to’g’ri birinchi argumentga ega bo’lgan user.login dan qisman funktsiya yarating:

    askPassword(user.login.bind(user, true), user.login.bind(user, false));
O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <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…)