Function Expression и функция как значение

До сих пор мы объявляли функции отдельно – function greet(name) {...} – а затем вызывали их. Но в JavaScript есть важная идея: сама функция тоже является значением.

В переменной можно хранить число или строку:

plain-values.jsjs
const count = 42;const title = 'Курс';

И точно так же – функцию:

expression.jsjs
const greet = function (name) {  return `Привет, ${name}!`;};

Такая запись называется Function Expression, или функциональное выражение:

Запись отличается от привычного объявления тремя деталями. Функция стоит справа от знака = и ведёт себя как обычное значение:

Имя после function можно не писать – такую функцию называют анонимной: чтобы вызвать её, достаточно имени переменной, в которой она лежит. Написать имя никто не запрещает, но в Function Expression оно обычно не нужно:

В конце ставим ;, потому что вся конструкция – это объявление переменной greet, такое же, как объявление count:

Функцию создали как значение и положили в переменную greet. Теперь greet можно вызывать точно так же, как функции из прошлых уроков:

greet-calls.jsjs
console.log(greet('Ира'));console.log(greet('Макс'));
Консоль
Привет, Ира!
Привет, Макс!

Запись const greet = function () {...}; можно мысленно читать так: «создай функцию и сохрани её в greet». Это очень похоже на обычное присваивание:

function-as-value.jsjs
const count = 42;const title = 'Курс';const greet = function () { /* ... */ };

Различается только содержимое. В greet лежит не результат работы функции, а ссылка на саму функцию – своего рода адрес, по которому её можно найти. Поэтому функцию можно вызвать, сохранить под другим именем или передать куда-нибудь дальше.

Значения, которые хранятся в переменной вот так, «по ссылке», называют ссылочными – подробно разберём их, когда познакомимся с объектами:

Убедиться, что в greet лежит именно функция, можно напрямую – напечатать переменную:

print.jsjs
const greet = function (name) {  return `Привет, ${name}!`;};console.log(greet);
Консоль
ƒ (name) {
return `Привет, ${name}!`;
}
В консоли – сама функция, её исходный код, а не результат вызова.

greet и greet() – не одно и то же

Круглые скобки меняют смысл записи. greet – это сама функция как значение. greet() – вызов этой функции и результат её работы.

Посмотрим на разницу по шагам:

Имя без скобок и со скобками
function double(x) {
Объявлена функция double.
return x * 2;
}
const twice = double;
const result = double(5);
console.log(twice(8));
console.log(result);
Переменные
double = функция

Главное здесь – строка const twice = double;. Скобок после double нет, поэтому функцию мы не вызываем. Мы берём саму функцию и сохраняем её ещё в одной переменной.

Теперь и double, и twice ведут к одной и той же функции. Вызвать её можно через любую из них:

alias.jsjs
const greet = function () {  console.log('Привет!');};const hello = greet;greet();hello();
Консоль
Привет!
Привет!

Теперь обе переменные указывают на одну и ту же функцию:

И это не просто удобная картинка. greet и hello действительно содержат ссылку на одну и ту же функцию – поэтому при строгом сравнении получим true:

same-function.jsjs
const greet = function () {  console.log('Привет!');};const hello = greet;console.log(greet === hello);
Консоль
true

true означает, что в обеих переменных находится одна и та же функция, а не две одинаковые копии.

Function Declaration и Function Expression

Теперь мы знаем два способа определить функцию. Поставим их рядом:

Function Declaration
function greet(name) {  return `Привет, ${name}!`;}
Function Expression
const greet = function (name) {  return `Привет, ${name}!`;};

После создания пользоваться обеими функциями можно одинаково: greet('Ира'). Но есть важное различие – с какого момента ими уже можно пользоваться.

Function Declaration доступна до строки объявления

declaration.jsjs
sayHi();function sayHi() {  console.log('Привет!');}
Консоль
Привет!

Хотя вызов стоит выше функции, код работает. Перед выполнением программы JavaScript заранее подготавливает объявления функций, поэтому к моменту первой строки sayHi уже доступна.

Посмотрим по шагам:

Объявление подготовлено заранее
sayHi();
function sayHi() {
До выполнения строк: JavaScript подготовил объявление – sayHi уже связана с функцией.
console.log('Привет!');
}
Переменные
sayHi = функция

Вот что JavaScript подготовил к моменту вызова:

Function Expression доступна только после присваивания

expression-early.jsjs
sayHi();const sayHi = function () {  console.log('Привет!');};
Консоль
ReferenceError: Cannot access 'sayHi' before initialization

С const sayHi = ... ситуация другая. JavaScript заранее знает, что переменная sayHi существует, но её значение станет доступно только тогда, когда выполнение дойдёт до строки присваивания. Поэтому вызов раньше этой строки заканчивается ReferenceError:

Зачем тогда нужен Function Expression?

После создания Function Declaration и Function Expression ведут себя во многом одинаково: обе функции можно вызывать, сохранять в других переменных и передавать дальше.

Особенность Function Expression в другом: функцию можно создать прямо там, где программе нужно значение. Например, сохранить её в переменную или сразу передать другой функции.

Последний вариант в JavaScript встречается постоянно:

anonymous.jsjs
runTwice(function () {  console.log('Привет!');});

Здесь функция создана прямо внутри вызова runTwice(...) – отдельное имя ей даже не понадобилось.

Что значит «передать функцию» и зачем это вообще нужно – разберём в следующем уроке.

// теперь вы знаете

  1. Function Expression позволяет создать функцию и сохранить её как обычное значение в переменной
  2. Функцию можно хранить в переменной и сохранить под другим именем: обе переменные будут указывать на одну функцию
  3. greet – сама функция, greet() – её вызов и результат этого вызова
  4. Function Declaration можно вызвать раньше строки объявления; Function Expression становится доступна через переменную только после выполнения присваивания

Проверьте себя

Что лежит в переменной greet после этой строки?

const greet = function () {
console.log('Привет!');
};

Что окажется в переменных twice и result?

function double(x) {
return x * 2;
}
const twice = double;
const result = double(5);

Что напечатает программа?

const greet = function () {
console.log('Привет!');
};
const hello = greet;
hello();
console.log(greet === hello);

Что произойдёт здесь?

sayHi();
function sayHi() {
console.log('Привет!');
}

Что произойдёт?

say();
const say = function () {
console.log('привет');
};

Какие вызовы сработают?

hello();
function hello() {
console.log('hello');
}
bye();
const bye = function () {
console.log('bye');
};

Выбери все верные варианты

переключают уроки · не работают при фокусе в полях ввода