20 KiB
Функции
Зачастую нам надо повторять одно и то же действие во многих частях программы.
Например, красиво вывести сообщение необходимо при приветствии посетителя, при выходе посетителя с сайта, еще где-нибудь.
Чтобы не повторять один и тот же код во многих местах, придуманы функции. Функции являются основными "строительными блоками" программы.
[cut]
Примеры встроенных функций вы уже видели -- это alert(message)
, prompt(message, default)
и confirm(question)
. Но можно создавать и свои.
Объявление
Пример объявления функции:
function showMessage() {
alert('Привет всем присутствующим!');
}
Вначале идет ключевое слово function
, после него имя функции, затем список параметров в скобках (в примере выше он пустой) и тело функции -- код, который выполняется при её вызове.
Объявленная функция доступна по имени, например:
//+ run
function showMessage() {
alert('Привет всем присутствующим!');
}
*!*
showMessage();
showMessage();
*/!*
Этот код выведет сообщение два раза. Уже здесь видна главная цель создания функций: избавление от дублирования кода.
Если понадобится поменять сообщение или способ его вывода -- достаточно изменить его в одном месте: в функции, которая его выводит.
Локальные переменные
Функция может содержать локальные переменные, объявленные через var
. Такие переменные видны только внутри функции:
//+ run
function showMessage() {
*!*
var message = 'Привет, я - Вася!'; // локальная переменная
*/!*
alert(message);
}
showMessage(); // 'Привет, я - Вася!'
alert(message); // <-- будет ошибка, т.к. переменная видна только внутри
Блоки if/else
, switch
, for
, while
, do..while
не влияют на область видимости переменных.
При объявлении переменной в таких блоках, она всё равно будет видна во всей функции.
Например:
function count() {
for (*!*var*/!* i=0; i<3; i++) {
*!*var*/!* j = i * 2;
}
*!*
alert(i); // i=3, на этом значении цикл остановился
alert(j); // j=4, последнее значение, на котором цикл сработал, было i=2
*/!*
}
Неважно, где именно в функции и сколько раз объявляется переменная. Любое объявление срабатывает один раз и распространяется на всю функцию.
Объявления переменных в примере выше можно передвинуть вверх, это ни на что не повлияет:
function count() {
*!*
var i, j; // передвинули объявления var в начало
*/!*
for (i=0; i<3; i++) {
j = i * 2;
}
alert(i); // i=3
alert(j); // j=4
}
Внешние переменные
Функция может обратиться ко внешней переменной, например:
//+ run
var *!*userName*/!* = 'Вася';
function showMessage() {
var message = 'Привет, я ' + *!*userName*/!*;
alert(message);
}
showMessage(); // Привет, я Вася
Доступ возможен не только на чтение, но и на запись. При этом, так как переменная внешняя, то изменения будут видны и снаружи функции:
//+ run
var *!*userName*/!* = 'Вася';
function showMessage() {
*!*
userName = 'Петя'; // (1) присвоение во внешнюю переменную
*/!*
var message = 'Привет, я ' + *!*userName*/!*;
alert(message);
}
showMessage();
*!*
alert(userName); // Петя, значение внешней переменной изменено функцией
*/!*
Конечно, если бы внутри функции, в строке (1)
, была бы объявлена своя локальная переменная var userName
, то все обращения использовали бы её, и внешняя переменная осталась бы неизменной.
[summary] Переменные, объявленные на уровне всего скрипта, называют "глобальными переменными".
Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта.
Пусть каждая функция работает "в своей песочнице". [/summary]
[warn header="Внимание: неявное объявление глобальных переменных!"]
В старом стандарте JavaScript существовала возможность неявного объявления переменных присвоением значения.
Например:
//+ run
function showMessage() {
message = 'Привет'; // без var!
}
showMessage();
alert(message); // Привет
В коде выше переменная message
нигде не объявлена, а сразу присваивается. Скорее всего, программист просто забыл поставить var
.
При use strict
такой код привёл бы к ошибке, но без него переменная будет создана автоматически, причём в примере выше она создаётся не в функции, а на уровне всего скрипта.
Избегайте этого.
Здесь опасность даже не в автоматическом создании переменной, а в том, что глобальные переменные должны использоваться тогда, когда действительно нужны "общескриптовые" параметры.
Забыли var
в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. Возможна ошибка и потеря времени на поиск проблемы.
[/warn]
В будущем, когда мы лучше познакомимся с основами JavaScript, в главе , мы более детально рассмотрим внутренние механизмы работы переменных и функций.
Параметры
При вызове функции ей можно передать данные, которые та использует по своему усмотрению.
Например, этот код выводит два сообщения:
//+ run
function showMessage(*!*from, text*/!*) { // параметры from, text
from = "** " + from + " **"; // здесь может быть сложный код оформления
alert(from + ': ' + text);
}
*!*
showMessage('Маша', 'Привет!');
showMessage('Маша', 'Как дела?');
*/!*
Параметры копируются в локальные переменные функции.
Например, в коде ниже есть внешняя переменная from
, значение которой при запуске функции копируется в параметр функции с тем же именем. Далее функция работает уже с параметром:
//+ run
function showMessage(from, text) {
*!*
from = '**' + from + '**'; // меняем локальную переменную (1)
*/!*
alert(from + ': ' + text);
}
var from = "Маша";
showMessage(from, "Привет");
alert(from); // "Маша", без изменений, так как в строке (1) была изменена копия значения
Здесь есть небольшая тонкость при работе с объектами. Как мы помним, в переменной хранится ссылка на объект. Поэтому функция, получив параметр-объект, работает с самим этим объектом:
Например, в коде ниже функция по ссылке меняет содержимое объекта user
:
//+ run
function makeAdmin(user) {
user.isAdmin = true;
}
var user = { name: "Вася" };
makeAdmin(user);
alert(user.isAdmin); // true
Стиль объявления функций
В объявлении функции есть правила для расстановки пробелов. Они отмечены стрелочками:

Конечно, вы можете ставить пробелы и по-другому, но эти правила используются в большинстве JavaScript-фреймворков.
Аргументы по умолчанию
Функцию можно вызвать с любым количеством аргументов.
Если параметр не передан при вызове -- он считается равным undefined
.
Например, функцию показа сообщения showMessage(from, text)
можно вызвать с одним аргументом:
showMessage("Маша");
При этом можно проверить, и если параметр не передан -- присвоить ему значение "по умолчанию":
//+ run
function showMessage(from, text) {
*!*
if (text === undefined) {
text = 'текст не передан';
}
*/!*
alert(from + ": " + text);
}
showMessage("Маша", "Привет!"); // Маша: Привет!
*!*
showMessage("Маша"); // Маша: текст не передан
*/!*
При объявлении функции необязательные аргументы, как правило, располагают в конце списка.
Для указания значения "по умолчанию", то есть, такого, которое используется, если аргумент не указан, используется два способа:
- Можно проверить, равен ли аргумент `undefined`, и если да -- то записать в него значение по умолчанию. Этот способ продемонстрирован в примере выше.
- Использовать оператор `||`:
//+ run function showMessage(from, text) { text = text || 'текст не передан'; ... }
Второй способ считает, что аргумент отсутствует, если передана пустая строка,
0
, или вообще любое значение, которое в булевом виде являетсяfalse
.
Если аргументов передано больше, чем надо, например showMessage("Маша", "привет", 1, 2, 3)
, то ошибки не будет. Но, чтобы получить такие "лишние" аргументы, нужно будет прочитать их из специального объекта arguments
, который мы рассмотрим в главе .
Возврат значения
Функция может возвратить результат, который будет передан в вызвавший её код.
Например, создадим функцию calcD
, которая будет возвращать дискриминант квадратного уравнения по формуле b2 - 4ac
:
//+ run
function calcD(a, b, c) {
*!*return*/!* b*b - 4*a*c;
}
var test = calcD(-4, 2, 1);
alert(test); // 20
Для возврата значения используется директива return
.
Она может находиться в любом месте функции. Как только до нее доходит управление -- функция завершается и значение передается обратно.
Вызовов return
может быть и несколько, например:
//+ run
function checkAge(age) {
if (age > 18) {
return true;
} else {
return confirm('Родители разрешили?');
}
}
var age = prompt('Ваш возраст?');
if (checkAge(age)) {
alert('Доступ разрешен');
} else {
alert('В доступе отказано');
}
Директива return
может также использоваться без значения, чтобы прекратить выполнение и выйти из функции.
Например:
function showMovie(age) {
if (!checkAge(age)) {
*!*
return;
*/!*
}
alert("Фильм не для всех"); // (*)
// ...
}
В коде выше, если сработал if
, то строка (*)
и весь код под ней никогда не выполнится, так как return
завершает выполнение функции.
[smart header="Значение функции без return
и с пустым return
"]
В случае, когда функция не вернула значение или return
был без аргументов, считается что она вернула undefined
:
//+ run
function doNothing() { /* пусто */ }
alert( doNothing() ); // undefined
Обратите внимание, никакой ошибки нет. Просто возвращается undefined
.
Ещё пример, на этот раз с return
без аргумента:
//+ run
function doNothing() {
return;
}
alert( doNothing() === undefined ); // true
[/smart]
Выбор имени функции [#function-naming]
Имя функции следует тем же правилам, что и имя переменной. Основное отличие -- оно должно быть глаголом, т.к. функция -- это действие.
Как правило, используются глагольные префиксы, обозначающие общий характер действия, после которых следует уточнение.
Функции, которые начинаются с "show"
-- что-то показывают:
showMessage(..) // префикс show, "показать" сообщение
Функции, начинающиеся с "get"
-- получают, и т.п.:
getAge(..) // get, "получает" возраст
calcD(..) // calc, "вычисляет" дискриминант
createForm(..) // create, "создает" форму
checkPermission(..) // check, "проверяет" разрешение, возвращает true/false
Это очень удобно, поскольку взглянув на функцию -- мы уже примерно представляем, что она делает, даже если функцию написал совсем другой человек, а в отдельных случаях -- и какого вида значение она возвращает.
[smart header="Одна функция -- одно действие"]
Функция должна делать только то, что явно подразумевается её названием. И это должно быть одно действие.
Если оно сложное и подразумевает поддействия -- может быть имеет смысл выделить их в отдельные функции? Зачастую это имеет смысл, чтобы лучше структурировать код.
...Но самое главное -- в функции не должно быть ничего, кроме самого действия и поддействий, неразрывно связанных с ним.
Например, функция проверки данных (скажем, "validate"
) не должна показывать сообщение об ошибке. Её действие -- проверить.
[/smart]
[smart header="Сверхкороткие имена функций"] Имена функций, которые используются очень часто, иногда делают сверхкороткими.
Например, во фреймворке jQuery есть функция $
, во фреймворке Prototype -- функция $$
, а в библиотеке LoDash очень активно используется функция с названием из одного символа подчеркивания _
.
[/smart]
Итого
Объявление функции имеет вид:
function имя(параметры, через, запятую) {
код функции
}
- Передаваемые значения копируются в параметры функции и становятся локальными переменными.
- Параметры функции копируются в её локальные переменные.
- Можно объявить новые локальные переменые при помощи `var`.
- Значение возвращается оператором `return ...`.
- Вызов `return` тут же прекращает функцию.
- Если `return;` вызван без значения, или функция завершилась без `return`, то её результат равен `undefined`.
При обращении к необъявленной переменной функция будет искать внешнюю переменную с таким именем, но лучше, если функция использует только локальные переменные:
- Это делает очевидным общий поток выполнения -- что передаётся в функцию и какой получаем результат.
- Это предотвращает возможные конфликты доступа, когда две функции, возможно написанные в разное время или разными людьми, неожиданно друг для друга меняют одну и ту же внешнюю переменную.
Именование функций:
- Имя функции должно понятно и чётко отражать, что она делает. Увидев её вызов в коде, вы должны тут же понимать, что она делает.
- Функция -- это действие, поэтому для имён функций, как правило, используются глаголы.
Функции являются основными строительными блоками скриптов. Мы будем неоднократно возвращаться к ним и изучать все более и более глубоко.