Funkční objekt, NFE

Jak již víme, funkce v JavaScriptu je hodnota.

Každá hodnota v JavaScriptu má svůj typ. Jaký typ je funkce?

V JavaScriptu jsou funkce objekty.

Dobrým způsobem, jak si představit funkce, jsou volatelné „akční objekty“. Můžeme je nejen volat, ale také s nimi zacházet jako s objekty:přidat/odebrat vlastnosti, předat odkaz atd.

Vlastnost „name“

Funkční objekty obsahují některé použitelné vlastnosti.

Například název funkce je přístupný jako vlastnost „name“:

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

alert(sayHi.name); // sayHi

Co je legrační, logika přiřazování jmen je chytrá. Funkce také přiřadí správný název, i když je vytvořena bez něj, a poté je okamžitě přiřazena:

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

alert(sayHi.name); // sayHi (there's a name!)

Funguje také, pokud je přiřazení provedeno pomocí výchozí hodnoty:

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

f();

Ve specifikaci se tato funkce nazývá „kontextový název“. Pokud funkce žádný neposkytuje, pak se v přiřazení zjistí z kontextu.

Objektové metody mají také názvy:

let user = {

 sayHi() {
 // ...
 },

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

}

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

Neexistuje však žádná magie. Jsou případy, kdy neexistuje způsob, jak zjistit správné jméno. V takovém případě je vlastnost name prázdná, jako zde:

// function created inside array
let arr = [function() {}];

alert( arr[0].name ); // <empty string>
// the engine has no way to set up the right name, so there is none

V praxi však většina funkcí má název.

Vlastnost „length“

Existuje další vestavěná vlastnost „length“, která vrací počet parametrů funkce, například:

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

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

Zde vidíme, že parametry odpočinku se nepočítají.

length vlastnost se někdy používá k introspekci ve funkcích, které fungují na jiných funkcích.

Například v kódu pod ask funkce přijímá question zeptat a libovolné číslo handler funkce k volání.

Jakmile uživatel zadá svou odpověď, funkce zavolá obsluhu. Můžeme předat dva druhy handlerů:

  • Funkce s nulovým argumentem, která je volána pouze tehdy, když uživatel odpoví kladně.
  • Funkce s argumenty, která je volána v obou případech a vrací odpověď.

Chcete-li zavolat na číslo handler správným způsobem prozkoumáme handler.length vlastnost.

Myšlenka je taková, že máme jednoduchou syntaxi obslužného programu bez argumentů pro pozitivní případy (nejčastější varianta), ale jsme schopni podporovat i univerzální obslužné nástroje:

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

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

}

// for positive answer, both handlers are called
// for negative answer, only the second one
ask("Question?", () => alert('You said yes'), result => alert(result));

Jedná se o konkrétní případ tzv. polymorfismu – zacházení s argumenty odlišně v závislosti na jejich typu nebo v našem případě v závislosti na length . Tento nápad má využití v knihovnách JavaScriptu.

Vlastní vlastnosti

Můžeme také přidat vlastní vlastnosti.

Zde přidáme counter vlastnost pro sledování celkového počtu hovorů:

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

 // let's count how many times we run
 sayHi.counter++;
}
sayHi.counter = 0; // initial value

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

alert( `Called ${sayHi.counter} times` ); // Called 2 times
Vlastnost není proměnná

Vlastnost přiřazená k funkci jako sayHi.counter = 0 není definovat lokální proměnnou counter uvnitř toho. Jinými slovy, vlastnost counter a proměnnou let counter jsou dvě nesouvisející věci.

S funkcí můžeme zacházet jako s objektem, ukládat do ní vlastnosti, ale to nemá žádný vliv na její provedení. Proměnné nejsou funkční vlastnosti a naopak. Toto jsou jen paralelní světy.

Funkční vlastnosti mohou někdy nahradit uzávěry. Například můžeme přepsat příklad funkce čítače z kapitoly Rozsah proměnné, uzavření, abychom použili vlastnost funkce:

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

count je nyní uložen přímo ve funkci, nikoli v jejím vnějším lexikálním prostředí.

Je to lepší nebo horší než použití uzávěru?

Hlavní rozdíl je v tom, že pokud je hodnota count žije ve vnější proměnné, pak k ní externí kód nemá přístup. Mohou jej měnit pouze vnořené funkce. A pokud je to vázáno na funkci, pak je taková věc možná:

function makeCounter() {

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

 counter.count = 0;

 return counter;
}

let counter = makeCounter();

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

Takže výběr implementace závisí na našich cílech.

Výraz pojmenované funkce

Named Function Expression neboli NFE je termín pro funkční výrazy, které mají název.

Vezměme si například běžný funkční výraz:

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

A přidejte k němu název:

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

Dosáhli jsme zde něčeho? Jaký je účel tohoto dodatečného "func" jméno?

Nejprve si všimněme, že stále máme funkční výraz. Přidání názvu "func" po function neudělal z něj deklaraci funkce, protože je stále vytvořen jako součást přiřazovacího výrazu.

Přidání takového jména také nic nezlomilo.

Funkce je stále dostupná jako sayHi() :

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

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

Na názvu func jsou dvě zvláštní věci , to jsou důvody:

  1. Umožňuje funkci interně odkazovat na sebe.
  2. Není vidět mimo funkci.

Například funkce sayHi níže se znovu zavolá s "Guest" pokud ne who je poskytováno:

let sayHi = function func(who) {
 if (who) {
 alert(`Hello, ${who}`);
 } else {
 func("Guest"); // use func to re-call itself
 }
};

sayHi(); // Hello, Guest

// But this won't work:
func(); // Error, func is not defined (not visible outside of the function)

Proč používáme func ? Možná stačí použít sayHi pro vnořené volání?

Ve většině případů můžeme:

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

Problém s tímto kódem je, že sayHi se může změnit ve vnějším kódu. Pokud je funkce místo toho přiřazena k jiné proměnné, kód začne dávat chyby:

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

let welcome = sayHi;
sayHi = null;

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

To se děje, protože funkce trvá sayHi ze svého vnějšího lexikálního prostředí. Neexistuje žádné místní sayHi , takže se použije vnější proměnná. A v okamžiku volání vnějšího sayHi je null .

Volitelný název, který můžeme vložit do funkčního výrazu, je určen k řešení přesně těchto druhů problémů.

Použijme to k opravě našeho kódu:

let sayHi = function func(who) {
 if (who) {
 alert(`Hello, ${who}`);
 } else {
 func("Guest"); // Now all fine
 }
};

let welcome = sayHi;
sayHi = null;

welcome(); // Hello, Guest (nested call works)

Nyní to funguje, protože název "func" je funkčně lokální. Není to foceno zvenčí (a není tam vidět). Specifikace zaručuje, že bude vždy odkazovat na aktuální funkci.

Vnější kód má stále svou proměnnou sayHi nebo welcome . A func je „název interní funkce“, způsob, jak se funkce může spolehlivě volat.

Pro deklaraci funkce nic takového neexistuje

Zde popsaná funkce „interní název“ je dostupná pouze pro výrazy funkcí, nikoli pro deklarace funkcí. Pro deklarace funkcí neexistuje žádná syntaxe pro přidání „interního“ názvu.

Někdy, když potřebujeme spolehlivý interní název, je to důvod k přepsání deklarace funkce na formu výrazu pojmenované funkce.

Shrnutí

Funkce jsou objekty.

Zde jsme popsali jejich vlastnosti:

  • name – název funkce. Obvykle převzato z definice funkce, ale pokud tam žádná není, JavaScript se ji pokusí uhodnout z kontextu (např. přiřazení).
  • length – počet argumentů v definici funkce. Zbývající parametry se nepočítají.

Pokud je funkce deklarována jako výraz funkce (ne v hlavním toku kódu) a nese název, nazývá se výraz pojmenované funkce. Název lze použít uvnitř k odkazování na sebe, pro rekurzivní volání nebo podobně.

Funkce mohou mít také další vlastnosti. Mnoho známých knihoven JavaScriptu tuto funkci skvěle využívá.

Vytvářejí „hlavní“ funkci a připojují k ní mnoho dalších „pomocných“ funkcí. Například knihovna jQuery vytvoří funkci s názvem $ . Knihovna lodash vytvoří funkci _ a poté přidá _.clone , _.keyBy a další vlastnosti (pokud se o nich chcete dozvědět více, podívejte se na dokumenty). Ve skutečnosti to dělají, aby snížili znečištění globálního prostoru, takže jediná knihovna poskytuje pouze jednu globální proměnnou. To snižuje možnost konfliktů pojmenování.

Funkce tedy může dělat užitečnou práci sama o sobě a také nést spoustu dalších funkcí ve vlastnostech.