Марейн Хавербеке - Выразительный JavaScript
Определение функции
Определение функции – обычное определение переменной, где значение, которое получает переменная, является функцией. Например, следующий код определяет переменную square, которая ссылается на функцию, подсчитывающую квадрат заданного числа:
var square = function(x) {
return x * x;
};
console.log(square(12));
// → 144
Функция создаётся выражением, начинающимся с ключевого слова function. У функций есть набор параметров (в данном случае, только x), и тело, содержащее инструкции, которые необходимо выполнить при вызове функции. Тело функции всегда заключают в фигурные скобки, даже если оно состоит из одной инструкции.
У функции может быть несколько параметров, или вообще их не быть. В следующем примере makeNoise не имеет списка параметров, а у power их целых два:
var makeNoise = function() {
console.log("Хрясь!");
};
makeNoise();
// → Хрясь!
var power = function(base, exponent) {
var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
};
console.log(power(2, 10));
// → 1024
Некоторые функции возвращают значение, как power и square, другие не возвращают, как makeNoise, которая производит только побочный эффект. Инструкция return определяет значение, возвращаемое функцией. Когда обработка программы доходит до этой инструкции, она сразу же выходит из функции, и возвращает это значение в то место кода, откуда была вызвана функция, return без выражения возвращает значение undefined.
Параметры и область видимости
Параметры функции – такие же переменные, но их начальные значения задаются при вызове функции, а не в её коде.
Важное свойство функций в том, что переменные, созданные внутри функции (включая параметры), локальны внутри этой функции. Это означает, что в примере с power переменная result будет создаваться каждый раз при вызове функции, и эти отдельные её инкарнации никак друг с другом не связаны.
Эта локальность переменных применяется только к параметрам и созданным внутри функций переменным. Переменные, заданные снаружи какой бы то ни было функции, называются глобальными, поскольку они видны на протяжении всей программы. Получить доступ к таким переменным можно и внутри функции, если только вы не объявили локальную переменную с тем же именем.
Следующий код иллюстрирует это. Он определяет и вызывает две функции, которые присваивают значение переменной x. Первая объявляет её как локальную, тем самым меняя только локальную переменную. Вторая не объявляет, поэтому работа с x внутри функции относится к глобальной переменной x, заданной в начале примера.
var x = "outside";
var f1 = function() {
var x = "inside f1";
};
f1();
console.log(x);
// → outside
var f2 = function() {
x = "inside f2";
};
f2();
console.log(x);
// → inside f2
Такое поведение помогает предотвратить случайное взаимодействие между функциями. Если бы все переменные использовались в любом месте программы, было бы очень трудно убедиться, что одна переменная не используется по разным назначениям. А если бы вы использовали переменную повторно, вы бы столкнулись со странными эффектами, когда сторонний код портит значения вашей переменной. Относясь к локальным для функций переменным так, что они существуют только внутри функции, язык делает возможной работу с функциями будто с отдельными маленькими вселенными, что позволяет не волноваться про весь код целиком.
Вложенные области видимости
JavaScript различает не только глобальные и локальные переменные. Функции можно задавать внутри функций, что приводит к нескольким уровням локальности.
К примеру, следующая довольно бессмысленная функция содержит внутри ещё две:
var landscape = function() {
var result = "";
var flat = function(size) {
for (var count = 0; count < size; count++)
result += "_";
};
var mountain = function(size) {
result += "/";
for (var count = 0; count < size; count++)
result += "'";
result += "\";
};
flat(3);
mountain(4);
flat(6);
mountain(1);
flat(1);
return result;
};
console.log(landscape());
// → ___/''''______/'_
Функции flat и mountain видят переменную result, потому что они находятся внутри функции, в которой она определена. Но они не могут видеть переменные count друг друга, потому что переменные одной функции находятся вне области видимости другой. А окружение снаружи функции landscape не видит ни одной из переменных, определённых внутри этой функции.
Короче говоря, в каждой локальной области видимости можно увидеть все области, которые её содержат. Набор переменных, доступных внутри функции, определяется местом, где эта функция описана в программе. Все переменные из блоков, окружающих определение функции, видны – включая и те, что определены на верхнем уровне в основной программе. Этот подход к областям видимости называется лексическим.
Люди, изучавшие другие языки программирования, могут подумать, что любой блок, заключённый в фигурные скобки, создаёт своё локальное окружение. Но в JavaScript область видимости создают только функции. Вы можете использовать отдельно стоящие блоки:
var something = 1;
{
var something = 2;
// Делаем что-либо с переменной something...
}
// Вышли из блока...
Но something внутри блока – это та же переменная, что и снаружи. Хотя такие блоки и разрешены, имеет смысл использовать их только для команды if и циклов.
Если это кажется вам странным – так кажется не только вам. В версии JavaScript 1.7 появилось ключевое слово let, которое работает как var, но создаёт переменные, локальные для любого данного блока, а не только для функции.
Функции как значения
Имена функций обычно используют как имя для кусочка программы. Такая переменная однажды задаётся и не меняется. Так что легко перепутать функцию и её имя.
Но это – две разные вещи. Вызов функции можно использовать, как простую переменную – например, использовать их в любых выражениях. Возможно хранить вызов функции в новой переменной, передавать её как параметр другой функции, и так далее. Также переменная, хранящая вызов функции, остаётся обычной переменной и её значение можно поменять:
var launchMissiles = function(value) {
missileSystem.launch("пли!");
};
if (safeMode)
launchMissiles = function(value) {/* отбой */};
В главе 5 мы обсудим чудесные вещи, которые возможно сделать, передавая вызовы функций другим функциям.
Объявление функций
Есть более короткая версия выражения “var square = function…”. Ключевое слово function можно использовать в начале инструкции:
function square(x) {
return x * x;
}
Это объявление функции. Инструкция определяет переменную square и присваивает ей заданную функцию. Пока всё ок. Есть только один подводный камень в таком определении.
console.log("The future says:", future());
function future() {
return "We STILL have no flying cars.";
}
Такой код работает, хотя функция объявляется ниже того кода, который её использует. Это происходит оттого, что объявления функций не являются частью обычного исполнения программ сверху вниз. Они «перемещаются» наверх их области видимости и могут быть вызваны в любом коде в этой области. Иногда это удобно, потому что вы можете писать код в таком порядке, который выглядит наиболее осмысленно, не беспокоясь по поводу необходимости определять все функции выше того места, где они используются.
А что будет, если мы поместим объявление функции внутрь условного блока или цикла? Не надо так делать. Исторически разные платформы для запуска JavaScript обрабатывали такие случаи по разному, а текущий стандарт языка запрещает так делать. Если вы хотите, чтобы ваши программы работали последовательно, используйте объявления функций только внутри других функций или основной программы.
function example() {
function a() {} // Нормуль
if (something) {
function b() {} // Ай-яй-яй!
}
}
Стек вызовов
Полезным будет присмотреться к тому, как порядок выполнения работает с функциями. Вот простая программа с несколькими вызовами функций: