6 sposobów deklarowania funkcji JavaScript

Funkcja jest parametrycznym blokiem kodu zdefiniowanym raz i wywoływanym później wiele razy. W JavaScript funkcja składa się z wielu komponentów i ma na nie wpływ:

  • Kod JavaScript, który tworzy ciało funkcji
  • Lista parametrów
  • Zmienne dostępne z zakresu leksykalnego
  • Wartość zwracana
  • Kontekst this kiedy funkcja jest wywoływana
  • .

  • Nazwa lub funkcja anonimowa
  • Zmienna, która przechowuje obiekt funkcji
  • arguments obiekt (lub brak w funkcji strzałki)

Ten post uczy sześciu podejść do deklarowania funkcji JavaScript: składni, przykładów i częstych pułapek. Co więcej, zrozumiesz, kiedy należy użyć konkretnego typu funkcji w pewnych okolicznościach.

1. Deklaracja funkcji

Deklaracja funkcji zbudowana jest ze słowa kluczowego function, po którym następuje obowiązkowa nazwa funkcji, lista parametrów w parze nawiasów (para1, ..., paramN) oraz para nawiasów klamrowych {...}, które ograniczają kod funkcji.

Przykład deklaracji funkcji:

// function declarationfunction isEven(num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false

function isEven(num) {...} to deklaracja funkcji, która definiuje isEven funkcję, która określa, czy liczba jest parzysta.

Deklaracja funkcji tworzy w bieżącym zakresie zmienną o identyfikatorze równym nazwie funkcji. Zmienna ta przechowuje obiekt funkcji.

Zmienna funkcji jest podnoszona do góry bieżącego zakresu, co oznacza, że funkcja może być wywołana przed deklaracją (więcej szczegółów w tym rozdziale).

Tworzona funkcja jest nazwana, co oznacza, że właściwość name obiektu funkcji przechowuje jej nazwę. Jest to przydatne podczas przeglądania stosu wywołań: w debugowaniu czy odczytywaniu komunikatów o błędach.

Poznajmy te właściwości na przykładzie:

// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name) // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}

Deklaracja funkcji function hello(name) {...} tworzy zmienną hello która jest podnoszona na górę bieżącego zakresu. hello zmienna przechowuje obiekt funkcji, a hello.name zawiera nazwę funkcji: 'hello'.

1.1 Funkcja regularna

Deklaracja funkcji pasuje do przypadków, gdy potrzebna jest funkcja regularna. Regularny oznacza, że deklarujesz funkcję raz, a później wywołujesz ją w wielu różnych miejscach. To jest podstawowy scenariusz:

function sum(a, b) { return a + b;}sum(5, 6); // => 11().reduce(sum) // => 10

Ponieważ deklaracja funkcji tworzy zmienną w bieżącym zakresie, obok regularnych wywołań funkcji, jest przydatna do rekurencji lub odłączania słuchaczy zdarzeń. W przeciwieństwie do wyrażeń funkcyjnych lub funkcji strzałkowych, które nie tworzą wiązania ze zmienną funkcji po jej nazwie.

Na przykład, aby obliczyć rekurencyjnie czynnik, musisz uzyskać dostęp do funkcji wewnątrz:

function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1);}factorial(4); // => 24

Wewnątrz factorial() wykonywane jest wywołanie rekurencyjne przy użyciu zmiennej przechowującej funkcję: factorial(n - 1).

Możliwe jest użycie wyrażenia funkcyjnego i przypisanie go do zmiennej regularnej, np. const factorial = function(n) {...}. Jednak deklaracja funkcji function factorial(n) jest zwarta (nie ma potrzeby stosowania const i =).

Ważną właściwością deklaracji funkcji jest jej mechanizm podnoszenia. Pozwala on na użycie funkcji przed deklaracją w tym samym zakresie.

Hoisting jest przydatny w niektórych sytuacjach. Na przykład wtedy, gdy chcemy zobaczyć, jak funkcja jest wywoływana na początku skryptu, bez czytania implementacji funkcji. Implementacja funkcji może znajdować się poniżej w pliku, więc możesz nawet tam nie przewijać.

Możesz przeczytać więcej szczegółów na temat podnoszenia deklaracji funkcji tutaj.

1.2 Różnica w stosunku do wyrażenia funkcji

Łatwo jest pomylić deklarację funkcji i wyrażenie funkcji. Wyglądają one bardzo podobnie, ale produkują funkcje o różnych właściwościach.

Łatwa do zapamiętania zasada: deklaracja funkcji w instrukcji zawsze zaczyna się od słowa kluczowego function. W przeciwnym razie jest to wyrażenie funkcyjne (patrz punkt 2.).

Poniższa próbka jest deklaracją funkcji, w której deklaracja zaczyna się od słowa kluczowego function:

// Function declaration: starts with "function"function isNil(value) { return value == null;}

W przypadku wyrażeń funkcyjnych deklaracja JavaScript nie zaczyna się od słowa kluczowego function (występuje ono gdzieś w środku kodu deklaracji):

// Function expression: starts with "const"const isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()const numbers = ().filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');

1.3 Deklaracja funkcji w instrukcjach warunkowych

Niektóre środowiska JavaScript mogą rzucić błąd referencji podczas wywoływania funkcji, której deklaracja pojawia się wewnątrz bloków {...} z iffor lub while instrukcji.
Włączmy tryb ścisły i zobaczmy, co się stanie, gdy funkcja zostanie zadeklarowana w bloku warunkowym:

(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();

Podczas wywoływania ok(), JavaScript rzuca ReferenceError: ok is not defined, ponieważ deklaracja funkcji znajduje się wewnątrz bloku warunkowego.

Deklaracja funkcji w warunkach jest dozwolona w trybie nierygorystycznym, co czyni ją jeszcze bardziej zagmatwaną.

Jako ogólna zasada dla tych sytuacji, kiedy funkcja powinna być tworzona przez warunki – użyj wyrażenia funkcyjnego. Zobaczmy jak to jest możliwe:

(function() { 'use strict'; let ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();

Ponieważ funkcja jest obiektem regularnym, przypisz ją do zmiennej zależnej od warunku. Wywołanie ok() działa dobrze, bez błędów.

2. Wyrażenie funkcyjne

Wyrażenie funkcyjne jest określane przez słowo kluczowe function, po którym występuje opcjonalna nazwa funkcji, lista parametrów w parze nawiasów (para1, ..., paramN) oraz para nawiasów klamrowych { ... }, które ograniczają kod funkcji.

Kilka próbek wyrażenia funkcyjnego:

const count = function(array) { // Function expression return array.length;}const methods = { numbers: , sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count(); // => 3methods.sum(); // => 14

Wyrażenie funkcyjne tworzy obiekt funkcji, który może być używany w różnych sytuacjach:

  • Przypisana do zmiennej jako obiekt count = function(...) {...}
  • Tworzy metodę na obiekcie sum: function() {...}
  • Użyj funkcji jako wywołania zwrotnego .reduce(function(...) {...})

Wyrażenie funkcji jest koniem roboczym w JavaScript. Zazwyczaj masz do czynienia z tego typu deklaracją funkcji, obok funkcji strzałkowej (jeśli wolisz krótką składnię i kontekst leksykalny).

2.1 Wyrażenie funkcji nazwanej

Funkcja jest anonimowa, gdy nie ma nazwy (name właściwość jest pustym ciągiem ''):

( function(variable) {return typeof variable; }).name; // => ''

To jest funkcja anonimowa, której nazwa jest pustym ciągiem.

Czasami nazwa funkcji może być wywnioskowana. Na przykład, gdy anonim jest przypisany do zmiennej:

const myFunctionVar = function(variable) { return typeof variable; };myFunctionVar.name; // => 'myFunctionVar'

Nazwa funkcji anonimowej to 'myFunctionVar', ponieważ myFunctionVar nazwa zmiennej jest używana do wnioskowania o nazwie funkcji.

Gdy wyrażenie ma podaną nazwę, jest to nazwane wyrażenie funkcyjne. Posiada ono pewne dodatkowe właściwości w porównaniu do zwykłego wyrażenia funkcyjnego:

  • Tworzona jest funkcja nazwana, tj. name właściwość przechowuje nazwę funkcji
  • Wewnątrz ciała funkcji zmienna o tej samej nazwie przechowuje obiekt funkcji

Użyjmy powyższego przykładu, ale ustawmy nazwę w wyrażeniu funkcji:

const getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number'console.log(getType.name); // => 'funName'console.log(typeof funName); // => 'undefined'

function funName(variable) {...} to nazwane wyrażenie funkcyjne. Zmienna funName jest dostępna w zakresie funkcji, ale nie poza nim. Tak czy inaczej, właściwość name obiektu funkcji przechowuje nazwę: funName.

2.2 Ulubione nazwane wyrażenie funkcyjne

Gdy wyrażenie funkcyjne const fun = function() {} jest przypisane do zmiennej, niektóre silniki wywnioskują nazwę funkcji z tej zmiennej. Jednak wywołania zwrotne mogą być przekazywane jako anonimowe wyrażenia funkcyjne, bez przechowywania w zmiennych: więc silnik nie może określić jego nazwy.

Rozsądne jest faworyzowanie nazwanych funkcji i unikanie anonimowych, aby uzyskać korzyści takie jak:

  • Wiadomości o błędach i stosy wywołań pokazują bardziej szczegółowe informacje, gdy używamy nazw funkcji
  • Wygodniejsze debugowanie poprzez zmniejszenie liczby anonimowych nazw stosów
  • Nazwa funkcji mówi, co funkcja robi
  • Możesz uzyskać dostęp do funkcji wewnątrz jej zakresu dla wywołań rekurencyjnych lub odłączania słuchaczy zdarzeń

3. Skrócona definicja metody

Skrócona definicja metody może być użyta w deklaracji metody na literałach obiektów i klasach ES2015. Można je zdefiniować za pomocą nazwy funkcji, a następnie listy parametrów w parze nawiasów (para1, ..., paramN) oraz pary nawiasów klamrowych { ... }, które ograniczają deklaracje ciała.

Następujący przykład wykorzystuje skróconą definicję metody w literale obiektu:

const collection = { items: , add(...items) { this.items.push(...items); }, get(index) { return this.items; }};collection.add('C', 'Java', 'PHP');collection.get(1) // => 'Java'

add() i get() Metody w obiekcie collection są zdefiniowane przy użyciu skróconej definicji metody. Metody te są wywoływane w zwykły sposób: collection.add(...) oraz collection.get(...).

Krótkie podejście do definicji metody ma kilka zalet w porównaniu z tradycyjnym definiowaniem właściwości za pomocą nazwy, dwukropka : i wyrażenia funkcyjnego add: function(...) {...}:

  • Krótsza składnia jest łatwiejsza do zrozumienia
  • Skrócona definicja metody tworzy nazwaną funkcję, w przeciwieństwie do wyrażenia funkcyjnego. Jest to przydatne do debugowania.

Składnia class wymaga deklaracji metod w krótkiej postaci:

class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}const sun = new Star('Sun');sun.getMessage(' is shining') // => 'Sun is shining'

3.1 Obliczone nazwy właściwości i metod

ECMAScript 2015 dodaje miłą cechę: obliczone nazwy właściwości w literałach obiektów i klasach.
Obliczone właściwości używają nieco innej składni () {...}, więc definicja metody wygląda w ten sposób:

const addMethod = 'add', getMethod = 'get';const collection = { items: , (...items) { this.items.push(...items); }, (index) { return this.items; }};collection('C', 'Java', 'PHP');collection(1) // => 'Java'

(...) {...} i (...) {...} to skrócone deklaracje metod z nazwami właściwości obliczonych.

4. Funkcja strzałkowa

Funkcję strzałkową definiuje się za pomocą pary nawiasów zawierających listę parametrów (param1, param2, ..., paramN), a następnie grubej strzałki => i pary nawiasów klamrowych {...}, które ograniczają deklaracje ciała.

Gdy funkcja strzałki ma tylko jeden parametr, parę nawiasów można pominąć. Gdy zawiera pojedynczą deklarację, można również pominąć nawiasy klamrowe.

Zobaczmy podstawowe zastosowanie funkcji strzałki:

const absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10absValue(5); // => 5

absValue

absValue to funkcja strzałki, która oblicza wartość bezwzględną liczby.

Funkcja zadeklarowana za pomocą fat arrow posiada następujące właściwości:

  • Funkcja arrow nie tworzy swojego kontekstu wykonania, lecz pobiera go leksykalnie (w przeciwieństwie do wyrażenia funkcyjnego lub deklaracji funkcji, które tworzą własny this w zależności od wywołania)
  • Funkcja arrow jest anonimowa. Jednak silnik może wywnioskować jej nazwę ze zmiennej przechowującej funkcję.
  • arguments obiekt nie jest dostępny w funkcji strzałki (w przeciwieństwie do innych typów deklaracji, które udostępniają arguments obiekt). Możesz jednak swobodnie używać parametrów odpoczynku (...params).

4.1 Przejrzystość kontekstu

this słowo kluczowe jest mylącym aspektem JavaScript (sprawdź ten artykuł dla szczegółowego wyjaśnienia na temat this).
Ponieważ funkcje tworzą własny kontekst wykonania, często trudno jest wykryć this wartość.

ECMAScript 2015 poprawia this użycie poprzez wprowadzenie funkcji strzałki, która pobiera kontekst leksykalnie (lub po prostu używa this z bezpośredniego zewnętrznego zakresu). Jest to miłe, ponieważ nie musisz używać .bind(this) lub przechowywać kontekstu var self = this, gdy funkcja potrzebuje otaczającego kontekstu.

Zobaczmy, jak this jest dziedziczony z funkcji zewnętrznej:

class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}const numbersObject = new Numbers();const addMethod = numbersObject.addNumber();addMethod(1);addMethod(5);console.log(numbersObject.array); // => 

Numbers Klasa przechowuje tablicę liczb i udostępnia metodę addNumber() do wstawiania nowych liczb.
Gdy addNumber() jest wywoływana bez argumentów, zwracana jest zamknięcie, które umożliwia wstawianie liczb. To zamknięcie jest funkcją strzałkową, która ma this jako numbersObject instancję, ponieważ kontekst jest pobierany leksykalnie z metody addNumbers().

Bez funkcji strzałki, musisz ręcznie naprawić kontekst. Oznacza to używanie obejść takich jak .bind() method:

//... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...

lub przechowywanie kontekstu w oddzielonej zmiennej var self = this:

//... const self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...

Przezroczystość kontekstu może być użyta, gdy chcesz zachować this w takiej postaci, w jakiej jest, zaczerpnięty z kontekstu załączającego.

4.2 Krótkie callbacki

Podczas tworzenia funkcji strzałkowej, pary nawiasów i nawiasy klamrowe są opcjonalne dla pojedynczego parametru i pojedynczej deklaracji ciała. Pomaga to w tworzeniu bardzo krótkich funkcji wywołania zwrotnego.

Zróbmy funkcję, która znajdzie, czy tablica zawiera 0:

const numbers = ;numbers.some(item => item === 0); // => true

item => item === 0 to funkcja strzałki, która wygląda prosto.

Zauważ, że zagnieżdżone krótkie funkcje strzałek są trudne do odczytania. Wygodnym sposobem użycia najkrótszej formy funkcji strzałki jest pojedyncze wywołanie zwrotne (bez zagnieżdżania).

Jeśli to konieczne, użyj rozszerzonej składni funkcji strzałek podczas pisania zagnieżdżonych funkcji strzałek. Jest to po prostu łatwiejsze do odczytania.

5. Funkcja Generator

Funkcja Generator w JavaScript zwraca obiekt Generator. Jej składnia jest podobna do wyrażenia funkcji, deklaracji funkcji lub deklaracji metody, tyle że wymaga znaku gwiazdki *.

Funkcję generatora można zadeklarować w następujących postaciach:

a. Formularz deklaracji funkcji function* <name>():

function* indexGenerator(){ var index = 0; while(true) { yield index++; }}const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1

b. Formularz wyrażenia funkcji function* ():

const indexGenerator = function* () { let index = 0; while(true) { yield index++; }};const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1

c. Formularz definicji metody skróconej *<name>():

const obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}const g = obj.indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1

We wszystkich 3 przypadkach funkcja generatora zwraca obiekt generatora g. Później g jest używany do generowania serii inkrementowanych liczb.

6. Jeszcze jedna rzecz: new Function

W JavaScript funkcje są obiektami pierwszej klasy – funkcja jest zwykłym obiektem typu function.
Powyżej opisane sposoby deklaracji tworzą ten sam typ obiektu funkcji. Zobaczmy przykład:

function sum1(a, b) { return a + b;}const sum2 = function(a, b) { return a + b;}const sum3 = (a, b) => a + b;console.log(typeof sum1 === 'function'); // => trueconsole.log(typeof sum2 === 'function'); // => trueconsole.log(typeof sum3 === 'function'); // => true

Typ obiektu function posiada konstruktor: Function.
Gdy Function jest wywoływany jako konstruktor new Function(arg1, arg2, ..., argN, bodyString), tworzona jest nowa funkcja. Argumenty arg1, args2, ..., argN przekazane do konstruktora stają się nazwami parametrów nowej funkcji, a ostatni argument bodyString jest używany jako kod ciała funkcji.

Utwórzmy funkcję, która sumuje dwie liczby:

const numberA = 'numberA', numberB = 'numberB';const sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25

sumFunction utworzona za pomocą Function konstruktora. wywołanie posiada parametry numberA i numberB oraz ciało return numberA + numberB.

Tworzone w ten sposób funkcje nie mają dostępu do bieżącego zakresu, dlatego nie można tworzyć domknięć. Są one zawsze tworzone w zakresie globalnym.

Jednym z możliwych zastosowań new Function jest lepszy sposób dostępu do obiektu globalnego w przeglądarce lub skrypcie NodeJS:

(function() { 'use strict'; const global = new Function('return this')(); console.log(global === window); // => true console.log(this === window); // => false})();

Pamiętajmy, że funkcje prawie nigdy nie powinny być deklarowane przy użyciu new Function(). Ponieważ ciało funkcji jest obliczane w czasie wykonywania, to podejście dziedziczy wiele eval() problemów z użytkowaniem: zagrożenia bezpieczeństwa, trudniejsze debugowanie, brak możliwości zastosowania optymalizacji silnika, brak autouzupełniania edytora.

7. Na koniec, który sposób jest lepszy?

Nie ma zwycięzcy ani przegranego. Decyzja, który typ deklaracji wybrać, zależy od sytuacji.

Istnieją jednak pewne zasady, którymi można się kierować w typowych sytuacjach.

Jeśli funkcja korzysta z this z funkcji zamykającej, dobrym rozwiązaniem jest funkcja strzałki. Gdy funkcja wywołania zwrotnego ma jedną krótką deklarację, funkcja strzałki jest również dobrym rozwiązaniem, ponieważ tworzy krótki i lekki kod.

Dla skrócenia składni podczas deklarowania metod na obiektach, preferowana jest skrócona deklaracja metody.

new Function sposób deklarowania funkcji normalnie nie powinien być używany. Głównie dlatego, że otwiera to potencjalne zagrożenia bezpieczeństwa, nie pozwala na autouzupełnianie kodu w edytorach i traci optymalizacje silnika.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *