Особенности функций в JavaScript

Материал из support.qbpro.ru
Версия для печати больше не поддерживается и может содержать ошибки обработки. Обновите закладки браузера и используйте вместо этого функцию печати браузера по умолчанию.

оригинал: http://learn.javascript.ru/function-declaration-expression


В этом разделе мы познакомимся c важными особенностями функций в JavaScript, а также с тремя способами объявить функцию.


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

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

Объявление создает функцию и присваивает в переменную.

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


function sayHi() {
 alert('Привет');
}
alert(sayHi); // выведет код функции

Здесь выводится не результат работы функции sayHi() (кстати, чему он равен?.. правильно, undefined, т.к. нет return), а сама функция, т.е. ее код.

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


function sayHi(person) {
 alert('Привет, ' + person);
}
var func = sayHi;
func('Вася'); // выведет 'Привет, Вася'
sayHi('Маша'); // и так по-прежнему работает

Копируется, конечно, не сама функция, а ссылка на неё. Получается, что обе переменные sayHi и func как бы указывают на одно и то же «место в памяти», где находится функция.


Объявление Function Declaration

Объявление функции, о котором мы говорили все время до этого, называется в спецификации языка Function Declaration.

Устоявшегося русского перевода нет, также такой синтаксис называют «стандартным» или «обычным» объявлением функции. Чуть дальше мы посмотрим другие варианты создания функций.

Позвольте еще раз выделить основной смысл объявления Function Declaration:

При объявлении функции создается переменная со значением-функцией. Не функция, а переменная с таким значением. Почувствуйте разницу.

Другими словами, объявление

function func(параметры) { код } 

говорит интерпретатору: «создай переменную func, и положи туда функцию с указанными параметрами и кодом».

При этом func — на самом деле не «имя функции», оно совсем никак (по стандарту) не привязано к функции! Это название переменной, в которую будет помещена функция, и из которой она может быть затем удалена, скопирована в другую переменную, и в результате её как func вызвать будет нельзя:

function func() { }
var g = func;
func = null;
func(); // ошибка! надо g()

В частности, невозможно иметь функцию и переменную с одинаковым именем:


// Нонсенс!
function f() { } // объявить переменную f и записать в нее функцию
var f = 5; // объявить переменную f (а она уже объявлена) и присвоить 5
alert(f); // 5

В примере выше переменная f в первой строке получает значение — функцию, а во второй — число 5. В итоге мы получим переменную со значением f=5.


Время создания Function Declaration

Функции, объявленные как Function Declaration, создаются в момент входа в область видимости.

Буквально, происходит следующее. Браузер видит скрипт. Сканирует его на предмет наличия Function Declaration — и для каждого создаёт функцию. (примечание — вложенные функции пока не трогаем, которые будут рассмотрены позже). А потом начинает выполнять код.

Поэтому функцию можно вызывать до объявления:

sayHi("Вася");
function sayHi(name) {
 alert("Привет, " + name);
}

Этот код будет работать, т.к. объявление function sayHi обрабатывается и функция создается до выполнения первой строчки кода.

Условно объявить функцию через Function Declaration нельзя

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

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

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

Чтобы это понять — вспомним, как работают функции.

Объявления обрабатываются до выполнения первой строчки кода. Браузер сканирует код и создает из таких объявлений функции. При этом второе объявление перезапишет первое.

Дальше, во время выполнения объявления игнорируются (они уже обработаны), как если бы код был таким:

var age = 18;
if (age >= 18) {} 
else {}
sayHi();

Как видно, конструкция if ни на что не влияет.

Это — правильное с точки зрения спецификации поведение. На момент написания этого раздела браузер Firefox обрабатывал этот код некорректно.


Объявление Function Expression

Существует альтернативный синтаксис для создания функций. Функцию можно создать и присвоить переменной как самое обычное значение: var f = функция.

Соответствующий способ объявления называется Function Expression и выглядит так:

var f = function(параметры) {
 // тело функции
}

В этом объявлении после function нет имени, создается «анонимная функция», которая затем присваивается переменной.

Например:

var sayHi = function(person) {
    alert("Привет, " + person);
}
sayHi('Вася');

В отличие от объявлений Function Declaration, которые создаются заранее, до выполнения кода, объявления Function Expression создают функцию, когда до них доходит выполнение.

Поэтому и пользоваться ими можно только после объявления.

sayHi(); // <-- работать не будет, функции еще нет
var sayHi = function() {  alert(1)  };

А вот так — будет:

var sayHi = function() {  alert(1)  };
sayHi(); // 1

Благодаря этому свойству Function Expression можно (и даже нужно) использовать для условного объявления функции:

var age = prompt('Сколько вам лет?');
var sayHi;
if (age >= 18) {
  sayHi = function() {  alert('Вход разрешен');  }
} else {
  sayHi = function() {  alert('Извините, вы слишком молоды');  }
}
sayHi(); // запустит ту из двух функций, которая присвоена выше 

Вызов «на месте»

Можно создать функцию при помощи Function Expression и не присваивать ее переменной, а тут же вызвать:

(function() {
  var a, b; // локальные переменные   
  // код
})();

Так делают, чтобы выполнить код, не загрязняя общую область видимости его локальными переменными.

Например, эта практика используется в библиотеках и фреймворках JavaScript. Дело в том, что библиотека при своей инициализации может использовать временные переменные и функции, которые могут конфликтовать с другим кодом.

Заключение таких переменных в функцию позволяет гарантировать отсутствие конфликтов с a и b из внешнего кода.

Поэтому вызов инициализации оборачивается в функцию и вызывается «на месте».


Зачем скобки вокруг функции?

В примере выше вокруг function() {...} находятся скобки. Если записать без них - такой код вызовет ошибку:


function() {
 // syntax error
}();

Эта ошибка произойдет потому, что браузер, видя ключевое слово function в основном потоке кода, попытается прочитать Function Declaration, а здесь даже имени нет.

Впрочем, даже если имя поставить, то работать тоже не будет:

function work() {
 // ...
}()  // syntax error

Дело в том, что «на месте» разрешено вызывать только Function Expression.

Общее правило таково:

Если браузер видит function в основном потоке кода - он считает, что это Function Declaration. Если же function идёт в составе более сложного выражения, то он считает, что это Function Expression. Для этого и нужны скобки - показать, что у нас Function Expression, который по правилам JavaScript можно вызвать «на месте».

Можно показать это другим способом, например так:

+function() { ... }()

Или так:

0*function() { ... }()

Главное, чтобы интерпретатор понял, что это Function Expression, тогда он позволит вызвать функцию «на месте».

Скобки не нужны, если это и так Function Expression, например в таком вызове:

var res = function(a,b) { return a+b }(2,2);
alert(res); // 4

Функция здесь создаётся как часть выражения присваивания, поэтому является Function Expression.


Для Function Expression тоже ставьте скобки

Технически, если функция заведомо является Function Expression, то она может быть вызвано «на месте» без скобок:

var result = function(a,b) {
  return a*b;
}(2,3);

Но из соображений стиля и читаемости скобки вокруг function рекомендуется ставить:

var result = (function(a,b) {
  return a*b;
})(2,3);

Тогда сразу видно, что в result записывается не сама функция (result = function...), а идёт «вызов на месте». При чтении такого кода меньше ошибок.


Создание через new Function

Существует еще один способ создания функции, который использует прямой вызов встроенного конструктора Function. Он принимает список аргументов и тело в виде строки, и генерирует функцию по ним:

var sayHi = new Function('name', ' alert("Привет, "+name) ');
sayHi("Вася");

Этот способ используется очень редко.

Предпочитайте Function Declaration

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

... код ...
var f = function() { ... }
...

То же самое с Function Declaration будет короче и читабельнее:

... код ...
function f() { ... }
...

Дополнительный бонус - такие функции можно вызывать до того, как они объявлены.

Используйте Function Expression только там, где это действительно нужно. Например, для объявления функции в зависимости от условий.

Итого

Функции в JavaScript являются значениями. Их можно присваивать, передавать, создавать в любом месте кода.

  • Если функция объявлена в основном потоке кода, то это Function Declaration.
  • Если функция создана как часть выражения, то это Function Expression.
  • Между этими двумя основными способами создания функций есть следующие различия:
Function Declaration Function Expression
Время создания До выполнения первой строчки кода. Когда управление достигает строки с функцией.
Можно вызвать до объявления Да (т.к. создается заранее) Нет
Можно объявить в if Нет (т.к. создается заранее) Да
Можно вызывать «на месте» Нет (ограничение синтаксиса JavaScript) Да