15 KiB
Функциональные выражения
В JavaScript функция является значением, таким же как строка или число.
Как и любое значение, объявленную функцию можно вывести, вот так:
//+ run
function sayHi() {
alert( "Привет" );
}
*!*
alert( sayHi ); // выведет код функции
*/!*
Обратим внимание на то, что в последней строке после sayHi
нет скобок. То есть, функция не вызывается, а просто выводится на экран.
Функцию можно скопировать в другую переменную:
//+ run
function sayHi() { // (1)
alert( "Привет" );
}
var func = sayHi; // (2)
func(); // Привет // (3)
sayHi = null;
sayHi(); // ошибка (4)
- Объявление `(1)` как бы говорит интерпретатору "создай функцию и помести её в переменную `sayHi`
- В строке `(2)` мы копируем функцию в новую переменную `func`. Ещё раз обратите внимание: после `sayHi` нет скобок. Если бы они были, то вызов `var func = sayHi()` записал бы в `func` *результат* работы `sayHi()` (кстати, чему он равен? правильно, `undefined`, ведь внутри `sayHi` нет `return`).
- На момент `(3)` функцию можно вызывать и как `sayHi()` и как `func()`
- ...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку.
Обычные значения, такие как числа или строки, представляют собой данные. А функцию можно воспринимать как действие.
Это действие можно запустить через скобки ()
, а можно и скопировать в другую переменную, как было продемонстрировано выше.
Объявление Function Expression [#function-expression]
Существует альтернативный синтаксис для объявления функции, который ещё более наглядно показывает, что функция -- это всего лишь разновидность значения переменной.
Он называется "Function Expression" (функциональное выражение) и выглядит так:
//+ run
var f = function(параметры) {
// тело функции
};
Например:
//+ run
var sayHi = function(person) {
alert("Привет, " + person);
};
sayHi('Вася');
Сравнение с Function Declaration
"Классическое" объявление функции, о котором мы говорили до этого, вида function имя(параметры) {...}
, называется в спецификации языка "Function Declaration".
- *Function Declaration* -- функция, объявленная в основном потоке кода.
- *Function Expression* -- объявление функции в контексте какого-либо выражения, например присваивания.
Несмотря на немного разный вид, по сути две эти записи делают одно и то же:
// Function Declaration
function sum(a, b) {
return a + b;
}
// Function Expression
var sum = function(a, b) {
return a + b;
}
Оба этих объявления говорят интерпретатору: "объяви переменную sum
, создай функцию с указанными параметрами и кодом и сохрани её в sum
".
Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором до выполнения кода.
Поэтому их можно вызвать до объявления, например:
//+ run
*!*
sayHi("Вася"); // Привет, Вася
*/!*
function sayHi(name) {
alert("Привет, " + name);
}
А если бы это было объявление Function Expression, то такой вызов бы не сработал:
//+ run
*!*
sayHi("Вася"); // ошибка!
*/!*
var sayHi = function(name) {
alert("Привет, " + name);
}
Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (их легко найти: они не являются частью выражений и начинаются со слова function
) и обрабатывает их.
А Function Expression создаются в процессе выполнении выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания sayHi = function...
Как правило, возможность Function Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код.
Можно расположить функции внизу, а их вызов -- сверху или наоборот.
Условное объявление функции [#bad-conditional-declaration]
В некоторых случаях "дополнительное удобство" Function Declaration может сослужить плохую службу.
Например, попробуем, в зависимости от условия, объявить функцию sayHi
по-разному:
//+ run
var age = +prompt("Сколько вам лет?", 20);
if (age >= 18) {
function sayHi() { alert('Прошу вас!'); }
} else {
function sayHi() { alert('До 18 нельзя'); }
}
sayHi();
При вводе 20
в примере выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает "До 18 нельзя"
, несмотря на то, что age = 20
.
В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции.
- Function Declaration обрабатываются перед запуском кода. Интерпретатор сканирует код и создает из таких объявлений функции. При этом второе объявление перезапишет первое.
- Дальше, во время выполнения, объявления Function Declaration игнорируются (они уже были обработаны). Это как если бы код был таким:
function sayHi() { alert('Прошу вас!'); } function sayHi() { alert('До 18 нельзя'); } var age = 20; if (age >= 18) { /* объявление было обработано ранее */ } else { /* объявление было обработано ранее */ } *!* sayHi(); // "До 18 нельзя", сработает всегда вторая функция */!*
...То есть, от
if
здесь уже ничего не зависит. По-разному объявить функцию, в зависимости от условия, не получилось.
Такое поведение соответствует современному стандарту. На момент написания этого раздела ему следуют все браузеры, кроме, как ни странно, Firefox.
Вывод: для условного объявления функций Function Declaration не годится.
А что, если использовать Function Expression?
//+ run
var age = prompt('Сколько вам лет?');
var sayHi;
if (age >= 18) {
sayHi = function() { alert('Прошу Вас!'); }
} else {
sayHi = function() { alert('До 18 нельзя'); }
}
sayHi();
Или даже так:
//+ run
var age = prompt('Сколько вам лет?');
var sayHi = (age >= 18) ?
function() { alert('Прошу Вас!'); } :
function() { alert('До 18 нельзя'); };
sayHi();
Оба этих варианта работают правильно, поскольку, в зависимости от условия, создаётся именно та функция, которая нужна.
Анонимные функции
Взглянем ещё на один пример.
Функция ask(question, yes, no)
предназначена для выбора действия в зависимости от результата f
.
Она выводит вопрос на подтверждение question
и, в зависимости от согласия пользователя, вызывает yes
или no
:
//+ run
*!*
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
*/!*
function showOk() {
alert("Вы согласились.");
}
function showCancel() {
alert("Вы отменили выполнение.");
}
// использование
ask("Вы согласны?", showOk, showCancel);
Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая ask
?
...Но при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым confirm
, а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё в своё время.
Здесь обратим внимание на то, что то же самое можно написать более коротко:
//+ run
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
*!*
ask(
"Вы согласны?",
function() { alert("Вы согласились."); },
function() { alert("Вы отменили выполнение."); }
);
*/!*
Здесь функции объявлены прямо внутри вызова ask(...)
, даже без присвоения им имени.
Функциональное выражение, которое не записывается в переменную, называют анонимной функцией.
Действительно, зачем нам записывать функцию в переменную, если мы не собираемся вызывать её ещё раз? Можно просто объявить непосредственно там, где функция нужна.
Такого рода код возникает естественно, он соответствует "духу" JavaScript.
new Function
Существует ещё один способ создания функции, который используется очень редко, но упомянем и его для полноты картины.
Он позволяет создавать функцию полностью "на лету" из строки, вот так:
//+ run
var sum = new Function('a,b', ' return a+b; ');
var result = sum(1,2);
alert(result); // 3
То есть, функция создаётся вызовом new Function(params, code)
:
- `params`
- Параметры функции через запятую в виде строки.
- `code`
- Код функции в виде строки.
Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения.
Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами.
Итого
Функции в JavaScript являются значениями. Их можно присваивать, передавать, создавать в любом месте кода.
- Если функция объявлена в *основном потоке кода*, то это Function Declaration.
- Если функция создана как *часть выражения*, то это Function Expression.
Между этими двумя основными способами создания функций есть следующие различия:
Function Declaration | Function Expression | |
---|---|---|
Время создания | До выполнения первой строчки кода. | Когда управление достигает строки с функцией. |
Можно вызвать до объявления | `Да` (т.к. создаётся заранее) | `Нет` |
Условное объявление в `if` | `Не работает` | `Работает` |
Иногда в коде начинающих разработчиков можно увидеть много Function Expression. Почему-то, видимо, не очень понимая происходящее, функции решают создавать как var func = function()
, но в большинстве случаев обычное объявление функции -- лучше.
Если нет явной причины использовать Function Expression -- предпочитайте Function Declaration.
Сравните по читаемости:
// Function Expression
var f = function() { ... }
// Function Declaration
function f() { ... }
Function Declaration короче и лучше читается. Дополнительный бонус -- такие функции можно вызывать до того, как они объявлены.
Используйте Function Expression только там, где это действительно нужно и удобно.