25 август 2025

Funktsiya obyekti, NFE

Bizga ma’lumki, JavaScript-dagi funktsiyalar qiymatlardir.

JavaScript-dagi har qanday qiymat turga ega. Funktsiya qaysi turga kiradi?

JavaScript-da funktsiyalar obyektlardir.

Funktsiyalarni tasavvur qilishning yaxshi usuli – bu chaqiriladigan “harakat obyektlari”. Biz ularni nafaqat chaqira olamiz, balki ularni obyektlar sifatida ko’rib chiqishimiz mumkin: xususiyatlarni qo’shish/olib tashlash, ma’lumotnoma orqali o’tish va hk.

“name” xususiyati

Funktsiya obyektlari bir nechta ishlatilishi mumkin bo’lgan xususiyatlarni o’z ichiga oladi.

Masalan, funktsiya nomiga “name” xususiyati sifatida kirish mumkin:

function sayHi() {
  alert("Salom");
}

alert(sayHi.name); // sayHi

Qizig’i shundaki, nom berish mantiqi juda aqlli. Shuningdek, tayinlashda ishlatiladigan funktsiyalarga to’g’ri nom beriladi:

let sayHi = function() {
  alert("Salom");
}

alert(sayHi.name); // sayHi (ishlaydi!)

Agar tayinlash oldingan berilgan qiymat orqali bajarilgan bo’lsa, u ham ishlaydi:

function f(sayHi = function() {}) {
  alert(sayHi.name); // sayHi (ishlaydi!)
}

f();

Spetsifikatsiyada ushbu xususiyat “kontekstual ism” deb nomlanadi. Agar funktsiya uni ta’minlamasa, topshiriqda u kontekstdan aniqlanadi.

Obyekt usullari ham nomlarga ega:

let user = {

  sayHi() {
    // ...
  },

  sayBye: function() {
    // ...
  }

}

alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye

Bunda hech sehr yo’q. To’g’ri ismni aniqlashning imkoni bo’lmagan holatlar ham mavjud. Bunday holda, ism xususiyati bo’sh, masalan:

// massiv ichida yaratilgan funktsiya
let arr = [function() {}];

alert( arr[0].name ); // <bo'sh matn>
// interpretatorda to'g'ri nomni o'rnatish imkoniyati yo'q, shuning uchun bo'sh

Amalda esa aksariyat funktsiyalarning nomi bor.

“length” xususiyati

Funktsiya parametrlari sonini qaytaradigan yana bir “length” xususiyati mavjud, masalan:

function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2

Bu erda dam qoldiq parametrlari hisoblanmaganligini ko’rishimiz mumkin.

length xususiyati ba’zan boshqa funktsiyalarda ishlaydigan funktsiyalarni introspektsiya qilish uchun ishlatiladi.

Masalan, quyida joylashgan kodda ask funktsiyasi so’raladigan savolni va chaqiruv qilish uchun tasodifiy sonlarni qabul qiladi.

Agar foydalanuvchi o’z javobini taqdim qilsa, funktsiya ishlovchilarni chaqiradi. Ikki xil ishlov beruvchidan o’tishimiz mumkin:

  • Nol-argumentli funktsiya, bu faqat foydalanuvchi ijobiy javob berganida chaqiriladi.
  • Ikkala holatda ham chaqiriladigan va javobni qaytaradigan argumentlarga ega funktsiya.

Ushbu g’oya shundaki, bizda ijobiy holatlar uchun argumentlarsiz ishlov beruvchi sintaksis mavjud (eng tez-tez uchraydigan variant), lekin universal ishlovchilarni ham taqdim etishga qodir.

handlers ni to’g’ri chaqirish uchun biz length xususiyatini ko’rib chiqamiz:

function ask(question, ...handlers) {
  let isYes = confirm(question);

  for(let handler of handlers) {
    if (handler.length == 0) {
      if (isYes) handler();
    } else {
      handler(isYes);
    }
  }

}

// ijobiy javob uchun ikkala ishlovchilar ham chaqiriladi
// salbiy javob uchun faqat ikkinchisi
ask("Savol?", () => alert('Siz ha dedingiz'), result => alert(result));

Bu polimorfizm deb nomlangan alohida holat – argumentlarni turiga qarab turlicha muomala qilish, yoki bizda length ga qarab. Ushbu g’oya JavaScript-ni kutubxonalarida ishlatilishi mumkin.

Maxsus xususiyatlar

Biz o’zimizga xos xususiyatlarni ham qo’shishimiz mumkin.

Bu erda biz chaqiruvlarning umumiy sonini kuzatib borish uchun counter xususiyatini qo’shamiz:

function sayHi() {
  alert("Hi");

  // keling, necha marta bajarilganini hisoblaymiz
  sayHi.counter++;
}
sayHi.counter = 0; // boshlang'ich qiymati

sayHi(); // Hi
sayHi(); // Hi

alert( `Called ${sayHi.counter} times` ); // 2 marta chaqirildi
Xususiyat o’zgaruvchan emas

sayHi.counter = 0 kabi funktsiyaga tayinlangan xususiyat ichida counter mahalliy o’zgaruvchanni aniqlamaydi. Boshqacha qilib aytganda, xususiyat counter va let counter – bu o’zaro bog’liq bo’lmagan ikkita narsa.

Biz funktsiyani obyekt sifatida ko’rib chiqishimiz, unda xususiyatlarni saqlashimiz mumkin, ammo bu uning bajarilishiga ta’sir qilmaydi. O’zgaruvchanlar hech qachon funktsiya xususiyatlaridan foydalanmaydi va aksincha. Bu shunchaki parallel olamlar.

Funktsiya xususiyatlari ba’zida yopilishni o’zgartirishi mumkin. Masalan, funktsiya xususiyatidan foydalanish uchun hisoblagich funktsiyasi misolini O'zgaruvchi doirasi, yopilish (closure) bobidan qayta yozishimiz mumkin:

function makeCounter() {
  // instead of:
  // let count = 0

  function counter() {
    return counter.count++;
  };

  counter.count = 0;

  return counter;
}

let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1

Endi counter tashqi leksik muhitida emas, balki to’g’ridan-to’g’ri funktsiyalarda saqlanadi.

Yopishni ishlatishdan ko’ra yaxshiroqmi yoki yomonmi?

Asosiy farq shundaki, agar count qiymati tashqi o’zgaruvchanda yashasa, tashqi kod unga kira olmaydi. Faqatgina ichki funktsiyalar uni o’zgartirishi mumkin. Agar u funktsiyaga bog’liq bo’lsa, unda bunday narsa bo’lishi mumkin:

function makeCounter() {

  function counter() {
    return counter.count++;
  };

  counter.count = 0;

  return counter;
}

let counter = makeCounter();

counter.count = 10;
alert( counter() ); // 10

Shunday qilib, amalga oshirishni tanlash bizning maqsadlarimizga bog’liq.

Named Function Expression

Named Function Expression, yoki NFE, nomi bo’lgan Funktsiya ifodalari uchun atama.

Masalan, oddiy funktsiya ifodasini olaylik:

let sayHi = function(who) {
  alert(`Salom, ${who}`);
};

Va unga ism qo’shaylik:

let sayHi = function func(who) {
  alert(`Salom, ${who}`);
};

Bu yerda biror narsaga erishdikmi? Ushbu qo’shimcha "func" nomining maqsadi nima?

Birinchidan, bizda hali ham funktsiya ifodasi borligini ta’kidlaymiz. function dan keyin "func" ismining qo’shilishi uni funktsiya deklaratsiyasiga aylantirmadi, chunki u hali ham tayinlash ifodasining bir qismi sifatida yaratilgan.

Bunday nomni qo’shish hech narsani buzmadi.

Funktsiya hanuzgacha sayHi() sifatida mavjud:

let sayHi = function func(who) {
  alert(`Salom, ${who}`);
};

sayHi("John"); // Salom, John

func nomi haqida ikkita alohida narsa mavjud:

  1. Bu funktsiya o’ziga havola qilishiga imkon beradi.
  2. Bu funksiya tashqaridan ko’rinmaydi.

Masalan, quyida joylashgan sayHi funktsiyasi yana o’zini "Mehmon" bilan chaqiradi, agar chaqirmasa, who taqdim etiladi:

let sayHi = function func(who) {
  if (who) {
    alert(`Salom, ${who}`);
  } else {
    func("Mehmon"); // o'zini qayta chaqirish uchun func-dan foydalaning
  }
};

sayHi(); // Salom, Guest

// Ammo bu ishlamaydi:
func(); // Error, func is not defined (funktsiya tashqaridan ko'rinmaydi)

Nima uchun func dan foydalandik? Ehtimol, ichki chaqiruv uchun sayHi dan foydalanamiz?

Darhaqiqat, aksariyat hollarda biz:

let sayHi = function(who) {
  if (who) {
    alert(`Salom, ${who}`);
  } else {
    sayHi("Mehmon");
  }
};

Ushbu kod bilan bog’liq muammo shundaki, sayHi qiymati o’zgarishi mumkin. Funktsiya boshqa o’zgaruvchanga o’tishi mumkin va kod xatolarga yo’l qo’yishni boshlaydi:

let sayHi = function(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    sayHi("Mehmon"); // Error: sayHi is not a function
  }
};

let welcome = sayHi;
sayHi = null;

welcome(); // Error, the nested sayHi call doesn't work any more!

Buning sababi shundaki, funktsiya sayHi ni tashqi leksik muhitdan oladi. Mahalliy sayHi yo’q, shuning uchun tashqi o’zgaruvchandan foydalaniladi. Chaqiruv paytida tashqi sayHi null.

Funktsiya ifodasiga kiritishimiz mumkin bo’lgan ixtiyoriy nom aynan shu turdagi muammolarni hal qilish uchun mo’ljallangan.

Kodimizni tuzatish uchun undan foydalanamiz:

let sayHi = function func(who) {
  if (who) {
    alert(`Salom, ${who}`);
  } else {
    func("Mehmon"); // Endi barchasi yaxshi
  }
};

let welcome = sayHi;
sayHi = null;

welcome(); // Salom, Mehmon (ichki chaqiruv ishlamoqda)

Endi u ishlaydi, chunki "func" nomi funktsional-lokal hisoblanadi. U tashqaridan olinmaydi (va u yerda ko’rinmaydi). Spetsifikatsiya har doim mavjud funktsiyaga murojaat qilishiga kafolat beradi.

Tashqi kod hali ham sayHi yoki welcome o’zgaruvchanga ega. Va func – bu “ichki funktsiya nomi”, funktsiya o’zini o’zi ichkaridan chaqira oladi.

Funktsiya deklaratsiyasi uchun bunday narsa yo’q

Bu yerda tasvirlangan “ichki ism” xususiyati faqat funktsiya ifodalari uchun mavjud, funktsiya deklaratsiyalari uchun emas. Funktsiya deklaratsiyalari uchun yana bitta “ichki” ism qo’shish uchun sintaksis imkoniyati mavjud emas.

Ba’zan, bizga ishonchli ichki nom kerak bo’lganda, funktsiya deklaratsiyasini nomlangan funktsiya ifodasi shakliga qayta yozish uchun sabab bo’ladi.

Xulosa

Funktsiyalar obyektlardir.

Bu yerda biz ularning xususiyatlarini ko’rib chiqdik:

  • name – funktsiya nomi. Nafaqat funktsiya ta’rifida berilganida, balki tayinlashlar va obyekt xususiyatlari uchun ham mavjud.
  • length – funktsiya ta’rifidagi argumentlar soni. Qoldiq parametrlari hisoblanmaydi.

Agar funktsiya funktsiya ifodasi deb e’lon qilingan bo’lsa (asosiy kod oqimida emas) va u nomni olib yuradigan bo’lsa, u holda Named Function Expression deyiladi. Ism ichida rekursiv chaqiruvlar yoki shunga o’xshash ma’lumotlarga murojaat qilish uchun ishlatilishi mumkin.

Shuningdek, funktsiyalar qo’shimcha xususiyatlarga ega bo’lishi mumkin. Ko’pgina taniqli JavaScript kutubxonalari ushbu xususiyatdan juda yaxshi foydalanadi.

Ular “asosiy” funktsiyani yaratadilar va unga boshqa ko’plab “yordamchi” funktsiyalarni biriktiradilar. Masalan, jquery kutubxonasi $ nomli funktsiyani yaratadi. Lodash kutubxonasi _ funktsiyasini yaratadi. Va keyin ularga _.clone, _.keyBy va boshqa xususiyatlarni qo’shib qo’yadilar (ular haqida ko’proq ma’lumotga ega bo’lishni xohlagan vaqtingizda docs ga qarang). Aslida, ular buni global makonning ifloslanishini kamaytirish uchun qilishadi, shuning uchun bitta kutubxona faqat bitta global o’zgaruvchanni beradi. Bu nizolarni nomlash imkoniyatini kamaytiradi.

Shunday qilib, funktsiya nafaqat o’z-o’zidan biror narsa qila oladi, balki uning xususiyatlari orqali foydali funksiyalarni ham ta’minlaydi.

Vazifalar

Hisoblagich ham kamayishi va raqamni o’rnatishi uchun makeCounter() kodini o’zgartiring:

  • counter() keyingi raqamni qaytarishi kerak (avvalgidek).
  • counter.set(value) count ni value ga o’rnatishi kerak.
  • counter.decrease() count ni 1 ga kamaytirishi kerak.

To’liq foydalanish namunasi uchun “sandbox” kodini ko’ring.

P.S. Joriy hisobni ushlab turish uchun siz yopilish yoki funktsiya xususiyatidan foydalanishingiz mumkin. Yoki ikkala variantni ham yozing.

Sinovlar bilan sandbox-ni oching.

Yechim mahalliy o’zgaruvchan count dan foydalanadi, ammo qo’shimcha usullar counter ga yoziladi. Ular bir xil tashqi leksik muhitga ega va shuningdek, hozirgi count ga kirishlari mumkin.

function makeCounter() {
  let count = 0;

  function counter() {
    return count++;
  }

  counter.set = (value) => (count = value);

  counter.decrease = () => count--;

  return counter;
}

Yechimni sandbox-dagi sinovlar bilan oching.

Quyidagi kabi ishlaydigan sum funktsiyasini yozing:

sum(1)(2) == 3; // 1 + 2
sum(1)(2)(3) == 6; // 1 + 2 + 3
sum(5)(-1)(2) == 6;
sum(6)(-1)(-2)(-3) == 0;
sum(0)(1)(2)(3)(4)(5) == 15;

P.S. Eslatma: funktsiyangiz uchun maxsus moslamani ibtidoiy konvertatsiyaga o’rnatishingiz kerak bo’lishi mumkin.

Sinovlar bilan sandbox-ni oching.

  1. Hammasi qanday bo’lishidan qat’iy nazar ishlashi uchun sum natijasi funktsiya bo’lishi kerak.
  2. Ushbu funktsiya chaqiruvlar orasidagi joriy qiymatni xotirada saqlashi kerak.
  3. Vazifaga ko’ra, funktsiya == ishlatilganda raqamga aylanishi kerak. Funktsiyalar obyektlardir, shuning uchun konvertatsiya Obyektlarni ibtidoiylarga aylantirish bobida tasvirlanganidek sodir bo’ladi va biz raqamni qaytaradigan o’z usulimizni taqdim etamiz…

Endi kod:

function sum(a) {
  let currentSum = a;

  function f(b) {
    currentSum += b;
    return f;
  }

  f.toString = function () {
    return currentSum;
  };

  return f;
}

alert(sum(1)(2)); // 3
alert(sum(5)(-1)(2)); // 6
alert(sum(6)(-1)(-2)(-3)); // 0
alert(sum(0)(1)(2)(3)(4)(5)); // 15

Iltimos e’tibor bering, sum funktsiyasi aslida bir marta ishlaydi. Bu f funktsiyasini qaytaradi.

Keyin har bir keyingi chaqiruvda f o’z parametrini currentSum yig’indisiga qo’shadi va o’zini qaytaradi.

f ning oxirgi satrida rekursiya mavjud emas.

Rekursiya nimaga o’xshashi:

function f(b) {
  currentSum += b;
  return f(); // <-- rekursiv chaqiruv
}

Va bizning holatimizda, biz faqatgina funktsiyani chaqirmasdan qaytaramiz:

function f(b) {
  currentSum += b;
  return f; // <-- o'zini o'zi chaqirmaydi, o'zini qaytaradi
}

Ushbu f keyingi chaqiruvda ishlatiladi, yana o’zimi qaytaradi, kerak bo’lganda. Keyin raqam yoki matn sifatida ishlatilganda toString currentSum qiymatini qaytaradi. Konvertatsiya qilish uchun biz bu yerda Symbol.toPrimitive yoki valueOf dan ham foydalanishimiz mumkin.

Yechimni sandbox-dagi sinovlar bilan oching.

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…)