Биз билгендей, JavaScript'те функция бул маани.
JavaScriptдеги ар бир маанинин түрү бар. Функциянын түрү кандай?
JavaScript'те функциялар объект болуп саналат.
Функцияны "бир нерсе кыла ала турган объект" деп ойлосоңуз болот. Сиз функцияларды чакырып гана тим болбостон, аларды кадимки объекттер сыяктуу колдоно аласыз: касиеттерди кошуу/алып салуу, аларды шилтеме аркылуу өткөрүп берүү ж.б.
"name" касиети
Функция объекти бир нече пайдалуу касиеттерди камтыйт.
Мисалы, функциянын аталышы бизге "name" касиети катары жеткиликтүү:
@A@function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
Таң калыштуусу, тапшырма логикасы name
абдан акылдуу. Функция аты жок түзүлүп, дароо дайындалса да, ал туура атты дайындайт, мисалы:
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (есть имя!)
Бул демейки маани дайындалса дагы иштейт:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (работает!)
}
f();@A@
Спецификацияда бул "контексттик аталыш" деп аталат: эгерде функциянын аты жок болсо, анда JavaScript аны контексттен аныктоого аракет кылат.
Объекттик ыкмалардын да аталыштары бар:
@A@let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye@A@
Бул жерде эч кандай сыйкыр жок. Бул туура аты аныктоо мүмкүн эмес болуп калат. Мындай учурларда, аталыш касиети бош. Мисалы:
@A@// функция объявлена внутри массива
let arr = [function() {}];
alert( arr[0].name ); // <пустая строка>
// здесь отсутствует возможность определить имя, поэтому его нет@A@
Бирок, иш жүзүндө бул сейрек кездешет, адатта функциялар name
.
"узундук" касиети
Дагы бир орнотулган "узундук" касиети анын декларациясында функциянын параметрлеринин санын камтыйт. Мисалы:
@A@function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2@A@
Көрүнүп тургандай, "калдык параметрлерди" билдирген эллипс, бул жерде "эсептебейт"
Мүлк кээде башка функцияларда иштеген функцияларда интроспекцияlength
үчүн колдонулат .
Мисалы, төмөндөгү коддо функция суроону жана жоопторду иштетүүчү функциялардын ыктыярдуу санын ask
параметр катары кабыл алат .question
handler
Колдонуучу суроого жооп бергенде, функция иштетүүчүлөрдү чакырат. Биз иштетүүчүлөрдүн эки түрүн өткөрө алабыз:
- Жооп ооба болгондо гана чакырыла турган аргументтери жок функция.
- Аргументтери бар функция эки учурда тең чакырылып, жооп кайтарат.
Иштөөчүгө handler
туура чакыруу үчүн, биз текшеребиз handler.length
.
Идея оң жооптор үчүн аргументтери жок жөнөкөй иштеткич синтаксисине ээ болуу (эң таралган учур), ошондой эле жалпы иштетүүчүлөрдү өткөрүү мүмкүнчүлүгү:
@A@function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// для положительных ответов вызываются оба типа обработчиков
// для отрицательных - только второго типа
ask("Вопрос?", () => alert('Вы ответили да'), result => alert(result));@A@
Бул Ad-hoc полиморфизминин өзгөчө учуру – аргументтердин түрүнө жараша же биздин учурда нын маанисине жараша иштетилиши length
. Бул идея JavaScript китепканаларында колдонмолорго ээ.
Ыңгайлаштырылган касиеттер
Биз өзүбүздүн касиеттерибизди да кошо алабыз.
counter
Чалуулардын жалпы санын эсепке алуу үчүн касиетти кошолу :
@A@function sayHi() {
alert("Hi");
// давайте посчитаем, сколько вызовов мы сделали
sayHi.counter++;
}
sayHi.counter = 0; // начальное значение
sayHi(); // Hi
sayHi(); // Hi
alert( `Вызвана ${sayHi.counter} раза` ); // Вызвана 2 раза@A@
катары дайындалган функция касиети анын ичиндеги локалдык өзгөрмө жарыялабайтsayHi.counter = 0
. Башка сөз менен айтканда, касиет жана өзгөрмө эки көз карандысыз нерсе.counter
counter
let counter
Биз функцияны объект катары колдоно алабыз, андагы касиеттерди сактай алабыз, бирок алар анын аткарылышына эч кандай таасир этпейт. Өзгөрмөлөр функциянын касиеттери эмес жана тескерисинче. Бул эки параллелдүү дүйнө.
Кээде жабуунун ордуна функциянын касиеттери колдонулушу мүмкүн. Мисалы, биз анын касиетин колдонуп Жабу бөлүмүндөгү эсептегич функцияны кайра жаза алабыз :
@A@function makeCounter() {
// вместо
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1@A@
Менчик count
азыр анын тышкы лексикалык чөйрөсүндө эмес, түздөн-түз функцияда сакталат.
Жабууну колдонуудан да жаманбы же жакшыбы?
Негизги айырмачылык, эгерде маани count
тышкы өзгөрмөдө жашаса, анда ал тышкы код үчүн жеткиликтүү эмес. Аны уяланган функциялар гана өзгөртө алат. Ал эми функция касиети катары дайындалса, анда биз аны ала алабыз:
@A@function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10@A@
Демек, ишке ашырууну тандоо биздин максаттарыбыздан көз каранды.
Функциянын аталышы
Аты аталган функция туюнтмасы же NFE аты бар Функция туюнтмасынын термини.
Мисалы, Функция туюнтмасын жарыялайлы:
@A@let sayHi = function(who) {
alert(`Hello, ${who}`);
};
Жана ага ат бериңиз:
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};@A@
Бул жерде биз эмнеге жетиштик? Бул кошумча ысымдын максаты эмне func
?
Биринчиден, функция дагы эле Функция туюнтмасы катары аныкталганына көңүл буруңуз. "func"
Кийин кошуу function
декларацияны Функция Декларациясына айлантпайт, анткени ал дагы эле дайындоо туюнтмасынын бир бөлүгү болуп саналат.
Мындай ысымды кошуу менен эч нерсе бузулбайт.
Функция дагы эле төмөнкүдөй жеткиликтүү sayHi()
:
@A@let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
sayHi("John"); // Hello, John@A@
func
Ал берилген ысымдын эки маанилүү өзгөчөлүгү бар :
- Бул функцияга өзүнө кайрылууга мүмкүндүк берет.
- Бул функциядан тышкары жеткиликтүү эмес.
Мисалы, төмөнкү функция эч кандай параметр өткөрүлбөсө, sayHi
өзүн чакырат :"Guest"
who
@A@let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // использует func, чтобы снова вызвать себя же
}
};
sayHi(); // Hello, Guest
// А вот так - не cработает:
func(); // Ошибка, func не определена (недоступна вне функции)@A@
Эмне үчүн колдонобуз func
? sayHi
Эмне үчүн жөн гана уяча чалуу үчүн колдонбойсуз ?
Жалпысынан алганда, биз муну жасай алабыз:
@A@let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest");
}
};@A@
Бирок, бул коддун көйгөйү бар, анын маанисин sayHi
өзгөртүүгө болот. Функция башка өзгөрмөгө дайындалышы мүмкүн, андан кийин код каталарды ыргыта баштайт:
@A@let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest"); // Ошибка: sayHi не является функцией
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Ошибка, вложенный вызов sayHi больше не работает!@A@
Бул функция sayHi
тышкы лексикалык чөйрөдөн алынгандыктан болот. sayHi
Жергиликтүү өзгөрмө жок болгондуктан , тышкы өзгөрмө колдонулат. Ал эми чакыруу учурунда, бул сырткы бир sayHi
барабар null
.
Функция туюнтмасына киргизүүгө боло турган кошумча ат ушул сыяктуу маселелерди чечүү үчүн иштелип чыккан.
Аны кодубузду оңдоо үчүн колдонолу:
@A@let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // Теперь всё в порядке
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hello, Guest (вложенный вызов работает)@A@
Азыр баары иштейт, анткени аты "func"
жергиликтүү жана функциянын ичинде. Азыр сырттан алынбайт (ал жактан жетүүгө болбойт). Спецификация ар дайым учурдагы функцияга кайрылаарына кепилдик берет.
Сырткы код дагы эле өзгөрмөлөрдү камтыйт sayHi
жана welcome
, бирок азыр func
"функциянын ички аты" болуп саналат, ошондуктан ал өзүн ички түрдө чакыра алат.
Жогоруда сүрөттөлгөн "ички" ат куулугу Функция туюнтмаларында гана иштейт жана Функция декларацияларында иштебейт . Функциянын декларациясы үчүн синтаксисте кошумча "ички" аталышты жарыялоо мүмкүнчүлүгү каралган эмес.
Көбүнчө, бизге ишенимдүү "ички" ат керек болгондо, Функциянын Декларациясын аталган Функция туюнтмасына кайра жазуу керек.
Бардыгы
Функциялар объект болуп саналат.
Алардын касиеттери:
name
– функциянын аталышы. Көбүнчө функциянын декларациясынан алынат, бирок анда жок болсо, JavaScript аны контексттен түшүнүүгө аракет кылат.length
функциянын декларациясындагы аргументтердин саны. Эллипсис («калдык параметрлер») каралбайт.
Эгерде функция Функция туюнтмасы катары жарыяланып (негизги код агымынан тышкары) жана аты болсо, анда ал Функциянын аталышы деп аталат. Бул ат өзүнө кайрылуу үчүн, рекурсивдүү чалуулар үчүн ж.б.
Функциялар кошумча касиеттерди да камтышы мүмкүн. Көптөгөн белгилүү JavaScript китепканалары бул функцияны акылдуу колдонушат.
Алар "негизги" функцияны түзүп, биринчисинин ичине көптөгөн "жардамчы" функцияларды кошот. Мисалы, jQuery китепканасы аталган функцияны түзөт $
. Lodash китепканасы функцияны түзүп , _
андан кийин ага жана башка касиеттерди кошот (бул тууралуу көбүрөөк билүү үчүн, документтерди караңыз ). Алар муну глобалдык аталыш мейкиндигинин булганышын азайтуу үчүн, ар бир китепканада бир гана глобалдык өзгөрмөгө ээ болуу менен ат конфликтинин мүмкүнчүлүгүн азайтат._.clone
_.keyBy
Ошентип, функция бир нерселерди өзү эле аткарбастан, өзүнүн касиеттери аркылуу пайдалуу функцияларды да камсыздай алат.
Tasks
Кодду makeCounter()
эсептегич азайтып, маани орното алышы үчүн өзгөртүңүз:
counter()
кийинки маанини кайтарышы керек (мурдагыдай).counter.set(value)
эсептегичти коюу керекvalue
.counter.decrease()
эсептегичтин маанисин 1ге азайтышы керек.
Толук колдонуу мисалы үчүн кумкорду кодун караңыз.
PS Эсептегичтин учурдагы маанисин сактоо үчүн, сиз функциянын жабылышын да, касиетин да колдонсоңуз болот. Же эки чечим чыгар: жана ошондой, жана.
sum
Бул сыяктуу иштей турган функцияны жазыңыз :
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
PS Кеңеш: балким, функция үчүн примитивге айландыруунун атайын ыкмасын жасашыңыз керек.