en.javascript.info/1-js/2-first-steps/18-function-declaration-expression/article.md
2015-01-09 01:39:01 +03:00

16 KiB
Raw Blame History

Функциональные выражения

В JavaScript функция является значением, таким же как строка или число.

Функция -- это значение

Как и любое значение, объявленную функцию можно вывести, вот так:

//+ run
function sayHi() { 
  alert( "Привет" ); 
}

*!*
alert( sayHi ); // выведет код функции
*/!*

Обратим внимание на то, что в последней строке после sayHi нет скобок. То есть, функция не вызывается, а просто выводится на экран.

Функцию можно скопировать в другую переменную:

//+ run
function sayHi() {   // (1)
  alert( "Привет" ); 
}

var func = sayHi;    // (2)
func(); // Привет    // (3)

sayHi = null;
sayHi();             // ошибка (4)
  1. Объявление `(1)` как бы говорит интерпретатору "создай функцию и помести её в переменную `sayHi`
  2. В строке `(2)` мы копируем функцию в новую переменную `func`. Ещё раз обратите внимание: после `sayHi` нет скобок. Если бы они были, то вызов `var func = sayHi()` записал бы в `func` *результат* работы `sayHi()` (кстати, чему он равен? правильно, `undefined`, ведь внутри `sayHi` нет `return`).
  3. На момент `(3)` функцию можно вызывать и как `sayHi()` и как `func()`
  4. ...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(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".

При этом название переменной, в которую записана функция, обычно называют "именем функции". Говорят: "функция 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 Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код.

Условное объявление функции [#bad-conditional-declaration]

В некоторых случаях "дополнительное удобство" Function Declaration может сослужить плохую службу.

Например, попробуем, в зависимости от условия, объявить функцию sayHi по-разному:

//+ run
var age = 20;

if (age >= 18) {
  function sayHi() { alert('Прошу вас!');  } 
} else {
  function sayHi() { alert('До 18 нельзя'); } 
}

sayHi();

[smart header="Зачем условное объявление?"] Конечно, можно произвести проверку условия внутри функции.

Но вынос проверки вовне даёт очевидный выигрыш в производительности в том случае, когда проверку достаточно произвести только один раз, и её результат никогда не изменится.

Например, мы можем проверить, поддерживает ли браузер определённые современные возможности, и если да -- функция будет использовать их, а если нет -- реализовать её другим способом. При этом проверка будет осуществляться один раз, на этапе объявления функции, а не при каждом запуске функции. [/smart]

При запуске примера выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает "До 18 нельзя", несмотря на то, что age = 20.

В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции.

  1. Function Declaration обрабатываются перед запуском кода. Интерпретатор сканирует код и создает из таких объявлений функции. При этом второе объявление перезапишет первое.
  2. Дальше, во время выполнения, объявления 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();

Оба этих варианта работают правильно, поскольку, в зависимости от условия, создаётся именно та функция, которая нужна.

Анонимные функции

Взглянем ещё на один пример.

Функция test(f, yes, no) получает три функции, вызывает первую и, в зависимости от её результата, вызывает вторую или третью:

//+ run
*!*
function test(f, yes, no) {
  if (f()) yes()
  else no();
}
*/!*

// вспомогательные функции
function f1() {
  return confirm("OK?");
}

function f2() {
  alert("Вы согласились.");
}

function f3() {
  alert("Вы отменили выполнение.");
}

// использование
test(f1, f2, f3);

В этом примере для нас, наверно, нет ничего нового. Подумаешь, объявили функции f1, f2, f3, передали их в качестве параметров другой функции (ведь функция -- обычное значение), вызвали те, которые нужны...

А вот то же самое, но более коротко:

//+ run
function test(f, yes, no) {
  if (f()) yes()
  else no();
}

*!*
test(
  function() { return confirm("OK?"); }, 
  function() { alert("Вы согласились."); },
  function() { alert("Вы отменили выполнение."); }
);
*/!*

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

Функциональное выражение, которое не записывается в переменную, называют анонимной функцией.

Действительно, зачем нам записывать функцию в переменную, если мы не собираемся вызывать её ещё раз? Можно просто объявить непосредственно там, где функция нужна.

Такого рода код возникает естественно, он соответствует "духу" 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 только там, где это действительно нужно и удобно.