Loading...

Функция объектиси, NFE

Функция объектиси, NFE

 

Биз билгендей, 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параметр катары кабыл алат .questionhandler

Колдонуучу суроого жооп бергенде, функция иштетүүчүлөрдү чакырат. Биз иштетүүчүлөрдүн эки түрүн өткөрө алабыз:

  • Жооп ооба болгондо гана чакырыла турган аргументтери жок функция.
  • Аргументтери бар функция эки учурда тең чакырылып, жооп кайтарат.

Иштөөчүгө 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 . Башка сөз менен айтканда, касиет жана өзгөрмө эки көз карандысыз нерсе.countercounterlet 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Ал берилген ысымдын эки маанилүү өзгөчөлүгү бар :

  1. Бул функцияга өзүнө кайрылууга мүмкүндүк берет.
  2. Бул функциядан тышкары жеткиликтүү эмес.

Мисалы, төмөнкү функция эч кандай параметр өткөрүлбөсө, 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@

Эмне үчүн колдонобуз funcsayHiЭмне үчүн жөн гана уяча чалуу үчүн колдонбойсуз ?

Жалпысынан алганда, биз муну жасай алабыз:

@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

маанилүүлүгү: 5

Кодду makeCounter()эсептегич азайтып, маани орното алышы үчүн өзгөртүңүз:

  • counter()кийинки маанини кайтарышы керек (мурдагыдай).
  • counter.set(value)эсептегичти коюу керек value.
  • counter.decrease()эсептегичтин маанисин 1ге азайтышы керек.

Толук колдонуу мисалы үчүн кумкорду кодун караңыз.

PS Эсептегичтин учурдагы маанисин сактоо үчүн, сиз функциянын жабылышын да, касиетин да колдонсоңуз болот. Же эки чечим чыгар: жана ошондой, жана.

Тапшырма үчүн тесттер менен кумкоргонду ачыңыз.

чечим
маанилүүлүгү: 2

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 Кеңеш: балким, функция үчүн примитивге айландыруунун атайын ыкмасын жасашыңыз керек.