Bizga malumki, 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 korib chiqishimiz mumkin: xususiyatlarni qoshish/olib tashlash, malumotnoma orqali otish va hk.
name xususiyati
Funktsiya obyektlari bir nechta ishlatilishi mumkin bolgan xususiyatlarni oz ichiga oladi.
Masalan, funktsiya nomiga name xususiyati sifatida kirish mumkin:
function sayHi() {
alert("Salom");
}
alert(sayHi.name); // sayHi
Qizigi shundaki, nom berish mantiqi juda aqlli. Shuningdek, tayinlashda ishlatiladigan funktsiyalarga togri nom beriladi:
let sayHi = function() {
alert("Salom");
}
alert(sayHi.name); // sayHi (ishlaydi!)
Agar tayinlash oldingan berilgan qiymat orqali bajarilgan bolsa, u ham ishlaydi:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (ishlaydi!)
}
f();
Spetsifikatsiyada ushbu xususiyat kontekstual ism deb nomlanadi. Agar funktsiya uni taminlamasa, 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 yoq. Togri ismni aniqlashning imkoni bolmagan holatlar ham mavjud. Bunday holda, ism xususiyati bosh, 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 korishimiz mumkin.
length xususiyati bazan boshqa funktsiyalarda ishlaydigan funktsiyalarni introspektsiya qilish uchun ishlatiladi.
Masalan, quyida joylashgan kodda ask funktsiyasi soraladigan savolni va chaqiruv qilish uchun tasodifiy sonlarni qabul qiladi.
Agar foydalanuvchi oz javobini taqdim qilsa, funktsiya ishlovchilarni chaqiradi. Ikki xil ishlov beruvchidan otishimiz mumkin:
- Nol-argumentli funktsiya, bu faqat foydalanuvchi ijobiy javob berganida chaqiriladi.
- Ikkala holatda ham chaqiriladigan va javobni qaytaradigan argumentlarga ega funktsiya.
Ushbu goya shundaki, bizda ijobiy holatlar uchun argumentlarsiz ishlov beruvchi sintaksis mavjud (eng tez-tez uchraydigan variant), lekin universal ishlovchilarni ham taqdim etishga qodir.
handlers ni togri chaqirish uchun biz length xususiyatini korib 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 goya JavaScript-ni kutubxonalarida ishlatilishi mumkin.
Maxsus xususiyatlar
Biz ozimizga xos xususiyatlarni ham qoshishimiz mumkin.
Bu erda biz chaqiruvlarning umumiy sonini kuzatib borish uchun counter xususiyatini qoshamiz:
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
sayHi.counter = 0 kabi funktsiyaga tayinlangan xususiyat ichida counter mahalliy ozgaruvchanni aniqlamaydi. Boshqacha qilib aytganda, xususiyat counter va let counter bu ozaro bogliq bolmagan ikkita narsa.
Biz funktsiyani obyekt sifatida korib chiqishimiz, unda xususiyatlarni saqlashimiz mumkin, ammo bu uning bajarilishiga tasir qilmaydi. Ozgaruvchanlar hech qachon funktsiya xususiyatlaridan foydalanmaydi va aksincha. Bu shunchaki parallel olamlar.
Funktsiya xususiyatlari bazida yopilishni ozgartirishi 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 togridan-togri funktsiyalarda saqlanadi.
Yopishni ishlatishdan kora yaxshiroqmi yoki yomonmi?
Asosiy farq shundaki, agar count qiymati tashqi ozgaruvchanda yashasa, tashqi kod unga kira olmaydi. Faqatgina ichki funktsiyalar uni ozgartirishi mumkin. Agar u funktsiyaga bogliq bolsa, unda bunday narsa bolishi 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 bogliq.
Named Function Expression
Named Function Expression, yoki NFE, nomi bolgan Funktsiya ifodalari uchun atama.
Masalan, oddiy funktsiya ifodasini olaylik:
let sayHi = function(who) {
alert(`Salom, ${who}`);
};
Va unga ism qoshaylik:
let sayHi = function func(who) {
alert(`Salom, ${who}`);
};
Bu yerda biror narsaga erishdikmi? Ushbu qoshimcha "func" nomining maqsadi nima?
Birinchidan, bizda hali ham funktsiya ifodasi borligini takidlaymiz. function dan keyin "func" ismining qoshilishi uni funktsiya deklaratsiyasiga aylantirmadi, chunki u hali ham tayinlash ifodasining bir qismi sifatida yaratilgan.
Bunday nomni qoshish 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:
- Bu funktsiya oziga havola qilishiga imkon beradi.
- Bu funksiya tashqaridan korinmaydi.
Masalan, quyida joylashgan sayHi funktsiyasi yana ozini "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 bogliq muammo shundaki, sayHi qiymati ozgarishi mumkin. Funktsiya boshqa ozgaruvchanga otishi mumkin va kod xatolarga yol qoyishni 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 yoq, shuning uchun tashqi ozgaruvchandan foydalaniladi. Chaqiruv paytida tashqi sayHi null.
Funktsiya ifodasiga kiritishimiz mumkin bolgan ixtiyoriy nom aynan shu turdagi muammolarni hal qilish uchun moljallangan.
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 korinmaydi). Spetsifikatsiya har doim mavjud funktsiyaga murojaat qilishiga kafolat beradi.
Tashqi kod hali ham sayHi yoki welcome ozgaruvchanga ega. Va func bu ichki funktsiya nomi, funktsiya ozini ozi ichkaridan chaqira oladi.
Bu yerda tasvirlangan ichki ism xususiyati faqat funktsiya ifodalari uchun mavjud, funktsiya deklaratsiyalari uchun emas. Funktsiya deklaratsiyalari uchun yana bitta ichki ism qoshish uchun sintaksis imkoniyati mavjud emas.
Bazan, bizga ishonchli ichki nom kerak bolganda, funktsiya deklaratsiyasini nomlangan funktsiya ifodasi shakliga qayta yozish uchun sabab boladi.
Xulosa
Funktsiyalar obyektlardir.
Bu yerda biz ularning xususiyatlarini korib chiqdik:
namefunktsiya nomi. Nafaqat funktsiya tarifida berilganida, balki tayinlashlar va obyekt xususiyatlari uchun ham mavjud.lengthfunktsiya tarifidagi argumentlar soni. Qoldiq parametrlari hisoblanmaydi.
Agar funktsiya funktsiya ifodasi deb elon qilingan bolsa (asosiy kod oqimida emas) va u nomni olib yuradigan bolsa, u holda Named Function Expression deyiladi. Ism ichida rekursiv chaqiruvlar yoki shunga oxshash malumotlarga murojaat qilish uchun ishlatilishi mumkin.
Shuningdek, funktsiyalar qoshimcha xususiyatlarga ega bolishi mumkin. Kopgina taniqli JavaScript kutubxonalari ushbu xususiyatdan juda yaxshi foydalanadi.
Ular asosiy funktsiyani yaratadilar va unga boshqa koplab yordamchi funktsiyalarni biriktiradilar. Masalan, jquery kutubxonasi $ nomli funktsiyani yaratadi. Lodash kutubxonasi _ funktsiyasini yaratadi. Va keyin ularga _.clone, _.keyBy va boshqa xususiyatlarni qoshib qoyadilar (ular haqida koproq malumotga ega bolishni xohlagan vaqtingizda docs ga qarang). Aslida, ular buni global makonning ifloslanishini kamaytirish uchun qilishadi, shuning uchun bitta kutubxona faqat bitta global ozgaruvchanni beradi. Bu nizolarni nomlash imkoniyatini kamaytiradi.
Shunday qilib, funktsiya nafaqat oz-ozidan biror narsa qila oladi, balki uning xususiyatlari orqali foydali funksiyalarni ham taminlaydi.
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)