8. Функции

Функция – это блок программного кода на языке JavaScript, который определяется один раз и может выполняться, или вызываться, многократно. Возможно, вы уже знакомы с понятием «функция» под другим названием, таким как подпрограмма, или процедура. Функции могут иметь параметры: определение функции может включать список идентификаторов, которые называются параметрами и играют роль локальных переменных в теле функции. При вызове функций им могут передаваться значения, или аргументы, соответствующие их параметрам. Функции часто используют свои аргументы для вычисления возвращаемого значения, которое является значением выражения вызова функции. В дополнение к аргументам при вызове любой функции ей передается еще одно значение, определяющее контекст вызова – значение в ключевом слове this.

Если функция присваивается свойству объекта, она называется методом объекта. Когда функция вызывается посредством объекта, этот объект становится контекстом вызова, или значением ключевого слова this. Функции, предназначенные для инициализации вновь созданных объектов, называются конструкторами. Конструкторы были описаны в разделе 6.1, и мы вернемся к ним в главе 9.

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

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

содержание 8.1. Определение функцийсодержание

Функции определяются с ключевым словом function, которое может использоваться в выражениях определения функций (раздел 4.3) или в инструкциях объявления функций (раздел 5.3.2). В любом случае определение функции начинается с ключевого слова function, за которым указываются следующие компоненты:

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

• Пара круглых скобок вокруг списка, пустого или с некоторым количеством идентификаторов, разделенных запятыми. Эти идентификаторы будут определять имена параметров функции и в теле функции могут использоваться как локальные переменные.

• Пара фигурных скобок с составной или пустой инструкций JavaScript внутри. Эти инструкции составляют тело функции: они выполняются при каждом вызове функции.

В примере 8.1 показано несколько определений функций в виде инструкций и выражений. Обратите внимание, что определения функций в виде выражений удобно использовать, только если они являются частью более крупных выражений, таких как присваивание или вызов функции, которые выполняют некоторые действия с помощью вновь объявленной функции.

Пример 8.1. Определения JavaScript-функций (Defining JavaScript functions)

Обратите внимание, что в выражениях определения функций имя функции может отсутствовать. Инструкция объявления функции фактически объявляет переменную и присваивает ей объект function. Выражение определения функции, напротив, не объявляет переменную. Однако в выражениях определения допускается указывать имя функции, которое может потребоваться в теле функции для вызова себя самой, как в функции вычисления факториала, приведенной выше. Если выражение определения функции включает имя, это имя будет ссылаться на объект function в области видимости данной функции. Фактически имя функции становится локальной переменной внутри тела функции. Большинство функций, определяемых выражениями, не нуждаются в именах, и их определение становится более компактным. Особенно удобно использовать выражения для определения однократно используемых функций, как в последних двух примерах.

Именование функций

В качестве имени функции может использоваться любой допустимый идентификатор. Старайтесь выбирать функциям достаточно описательные, но не длинные имена. Искусство сохранения баланса между краткостью и информативностью приходит с опытом. Правильно подобранные имена функций могут существенно повысить удобочитаемость (а значит, и простоту сопровождения) ваших программ.

Чаще всего в качестве имен функций выбираются глаголы или фразы, начинающиеся с глаголов. По общепринятому соглашению имена функций начинаются со строчной буквы. Если имя состоит из нескольких слов, в соответствии с одним из соглашений они отделяются друг от друга символом подчеркивания, примерно так: like_this(), по другому соглашению все слова, кроме первого, начинаются с прописной буквы, примерно так: likeThis(). Имена функций, которые, как предполагается, реализуют внутреннюю, скрытую от посторонних глаз функциональность, иногда начинаются с символа подчеркивания.

В некоторых стилях программирования или в четко определенных программных платформах бывает полезно давать наиболее часто используемым функциям очень короткие имена. Примером может служить библиотека jQuery клиентского JavaScript (описываемая в главе 19), в public API которой широко используется функция с именем $() (да–да, просто знак доллара). (В разделе 2.4 уже говорилось, что в идентификаторах JavaScript помимо алфавитно-цифровых символов допускается использовать знаки доллара и подчеркивания.)

Как описывалось в разделе 5.3.2, инструкции объявления функций «поднимаются» в начало содержащих их сценария или функции, благодаря чему объявленные таким способом функции могут вызываться в программном коде выше объявления. Это не относится к функциям, которые определяются в виде выражений: чтобы вызвать функцию, необходимо иметь возможность сослаться на нее, однако нельзя сослаться на функцию, которая определяется с помощью выражения, пока она не будет присвоена переменной. Объявления переменных также поднимаются вверх (раздел 3.10.1), но операции присваивания значений этим переменным не поднимаются, поэтому функции, определяемые в виде выражений, не могут вызываться до того, как они будут определены.

Обратите внимание, что большинство (но не все) функций в примере 8.1 содержат инструкцию return (раздел 5.6.4). Инструкция return завершает выполнение функции и выполняет возврат значения своего выражения (если указано) вызывающей программе. Если выражение в инструкции return отсутствует, она возвращает значение undefined. Если инструкция return отсутствует в функции, интерпретатор просто выполнит все инструкции в теле функции и вернет вызывающей программе значение undefined.

Большинство функций в примере 8.1 вычисляют некоторое значение, и в них инструкция return используется для возврата этого значения вызывающей программе. Функция printprops() несколько отличается в этом смысле: ее работа заключается в том, чтобы вывести имена свойств объекта. Ей не нужно возвращать какое-либо значение, поэтому в функции отсутствует инструкция return. Функция printprops() всегда будет возвращать значение undefined. (Функции, не имеющие возвращаемого значения, иногда называются процедурами.)

содержание 8.1.1. Вложенные функции

В JavaScript допускается вложение функций в другие функции. Например:
  function hypotenuse(a, b) {
    function square(x) { return x*x; }
    return Math.sqrt(square(a) + square(b));
  }

Особый интерес во вложенных функциях представляют правила видимости переменных: они могут обращаться к параметрам и переменным, объявленным во вмещающей функции (или функциях). Например, в определении выше внутренняя функция square() может читать и изменять параметры a и b, объявленные во внешней функции hypotenuse(). Эти правила видимости, действующие для вложенных функций, играют важную роль, и мы еще вернемся к ним в разделе 8.6.

Как отмечалось в разделе 5.3.2, инструкции объявления функций в действительности не являются настоящими инструкциями, и спецификация ECMAScript допускает использовать их только в программном коде верхнего уровня. Они могут появляться в глобальном программном коде или внутри других функций, но они не могут находиться внутри циклов, условных инструкций, инструкций try/catch/finally или with

(некоторые реализации JavaScript могут иметь менее строгие требования. Например, в браузере Firefox допускается наличие «условных определений функций» внутри инструкций if).

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

содержание 8.2. Вызов функций содержание

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

• как функции,
• как методы,
• как конструкторы и
• косвенно, с помощью их методов call() и apply() .

содержание 8.2.1. Вызов функций

Вызов функций как функций или как методов выполняется с помощью выражения вызова (раздел 4.5). Выражение вызова состоит из выражения обращения к функции, которое возвращает объект function, и следующими за ним круглыми скобками со списком из нуля или более выражений–аргументов, разделенных запятыми, внутри. Если выражение обращения к функции является выражением обращения к свойству (если функция – свойство объекта или элемент массива) то выражение вызова – это выражение вызова метода. Такой случай будет описан ниже. В следующем фрагменте демонстрируется несколько примеров выражений вызова обычных функций:

При вызове функции вычисляются все выражения–аргументы (указанные в скобках), и полученные значения используются в качестве аргументов функции. Эти значения присваиваются параметрам, имена которых перечислены в определении функции. В теле функции выражения обращений к параметрам возвращают значения соответствующих аргументов.

При вызове обычной функции возвращаемое функцией значение становится значением выражения вызова. Если возврат из функции происходит по достижении ее конца интерпретатором, возвращается значение undefined. Если возврат из функции происходит в результате выполнения инструкции return, возвращается значение выражения, следующего за инструкцией return, или undefined, если инструкция return не возвращает значение.

При вызове функции в ECMAScript 3 и в нестрогом режиме ECMAScript 5 контекстом вызова (значением this) является глобальный объект. Однако в строгом режиме контекстом вызова является значение undefined.

Функции, написанные для вызовов именно как функции, обычно вовсе не используют ключевое слово this. Впрочем, его можно использовать, чтобы определить, не выполняется ли функция в строгом режиме:

// Определение и вызов функции, которая выясняет действующий режим работы:
var strict = (function() { return !this; }());

содержание 8.2.2. Вызов методов

Метод – это не что иное, как JavaScript-функция, которая хранится как свойство объекта. Если имеется функция f и объект o, то можно определить метод объекта o с именем m, как показано ниже:

o.m = f;

После этого можно вызвать метод m() объекта o:

o.m();

Или, если метод m() принимает два аргумента, его можно вызвать так:

o.m(x, y);

Этот код является выражением вызова: он включает выражение обращения к функции o.m и два выражения–аргумента, x и y. Выражение обращения к функции в свою очередь является выражением обращения к свойству (раздел 4.4), а это означает, что функция вызывается как метод, а не как обычная функция.

Аргументы и возвращаемое значение при вызове метода обрабатываются точно так же, как при вызове обычной функции. Однако вызов метода имеет одно важное отличие: контекст вызова. Выражение обращения к свойству состоит из двух частей: объекта (в данном случае o) и имени свойства (m). В подобных выражениях вызова методов объект o становится контекстом вызова, и тело функции получает возможность ссылаться на этот объект с помощью ключевого слова this. Например:

Чаще всего при вызове методов используется форма с оператором точки, однако точно так же можно обращаться к свойствам–методам с помощью квадратных скобок. Например, оба следующих выражения являются выражениями вызова методов:

Выражения вызова методов могут включать более сложные выражения обращения к свойствам:

Методы и ключевое слово this занимают центральное место в парадигме объектно-ориентированного программирования. Любая функция, используемая как метод, фактически получает неявный аргумент – объект, относительно которого она была вызвана. Как правило, методы выполняют некоторые действия с объектом, и синтаксис вызова метода наглядно отражает тот факт, что функция оперирует объектом. Сравните следующие две строки:

rect.setSize(width, height):
setRectSize(rect, width, height);

Гипотетически функции, вызывающиеся в этих двух строках, могут производить абсолютно идентичные действия над объектом rect (гипотетическим), но синтаксис вызова метода в первой строке более наглядно демонстрирует, что в центре внимания находится объект rect.

Обратите внимание: this – это именно ключевое слово, а не имя переменной или свойства. Синтаксис JavaScript не допускает возможность присваивания значений элементу this.

В отличие от переменных, ключевое слово this не имеет области видимости, и вложенные функции не наследуют значение this от вызывающей функции. Если вложенная функция вызывается как метод, значением this является объект, относительно которого был сделан вызов. Если вложенная функция вызывается как функция, то значением this будет либо глобальный объект (в нестрогом режиме), либо undefined (в строгом режиме). Распространенная ошибка полагать, что во вложенной функции, которая вызывается как функция, можно использовать this для получения доступа к контексту внешней функции. Если во вложенной функции необходимо иметь доступ к значению this внешней функции, это значение следует сохранить в переменной, находящейся в области видимости внутренней функции. Для этой цели часто используется переменная с именем self. Например:

В примере 8.5 (раздел 8.7.4) демонстрируется более практичный способ использования идиомы var self=this.

Составление цепочек вызовов методов

Когда методы возвращают объекты, появляется возможность использовать значение, возвращаемое одним методом, как часть последующих вызовов. Это позволяет создавать последовательности («цепочки», или «каскады») вызовов методов в одном выражении. При работе с библиотекой jQuery (глава 19), например, часто можно встретить такие инструкции:

// Отыскать все заголовки, отобразить их в значения атрибутов id,
// преобразовать в массив и отсортировать
$(":header").map(function() { return this.id }).get().sort();

Если вы пишете метод, не имеющий собственного возвращаемого значения, подумайте о возможности возвращать из него значение this. Если неуклонно следовать этому правилу при разработке своего API, появится возможность использовать стиль программирования, известный как составление цепочек из методов (термин был введен Мартином Фаулером (Martin Fowler), см. http://martinfowler.com/dslCatalog/methodChaining.html), когда обращение к имени метода выполняется один раз, а затем может следовать множество вызовов его методов:

shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();

He путайте цепочки вызовов методов с цепочками конструкторов, которые описываются в разделе 9.7.2.

содержание 8.2.3. Вызов конструкторов

Если вызову функции или метода предшествует ключевое слово new, то это вызов конструктора (вызовы предварительно конструкторов обсуждались в разделах 4.6 и 6.1.2, а более подробно конструкторы будут рассматриваться в главе 9). Вызов конструктора отличается от вызова обычной функции или метода особенностями обработки аргументов, контекстом вызова и возвращаемым значением.

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

var o = new Object();
var o = new Object;

Вызов конструктора создает новый пустой объект, наследующий свойство prototype конструктора. Назначение функции–конструктора – инициализировать объект, и этот вновь созданный объект передается конструктору как контекст вызова, благодаря чему функция–конструктор может ссылаться на него с помощью ключевого слова this. Обратите внимание, что вновь созданный объект передается как контекст вызова, даже если вызов конструктора выглядит как вызов метода. То есть в выражении new o.m() контекстом вызова будет вновь созданный объект, а не объект o.

Как правило, в функциях–конструкторах не используется инструкция return. Обычно они выполняют инициализацию нового объекта и неявно возвращают его по достижении своего конца. В этом случае значением выражения вызова конструктора становится новый объект. Однако если конструктор явно использует инструкцию return, чтобы вернуть объект, то этот объект становится значением выражения вызова. Если конструктор использует инструкцию return и она не возвращает значения или вернет простое значение, это возвращаемое значение будет проигнорировано, а результатом вызова будет новый объект.

содержание 8.2.4. Косвенный вызов

Функции в языке JavaScript являются объектами и подобно другим объектам имеют свои методы. В их числе есть два метода, call() и apply(), выполняющие косвенный вызов функции. Оба метода позволяют явно определить значение this для вызываемой функции, что дает возможность вызывать любую функцию как метод любого объекта, даже если фактически она не является методом этого объекта. Кроме того, обоим методам можно передать аргументы вызова. Метод call() позволяет передавать аргументы для вызываемой функции в своем собственном списке аргументов, а метод apply() принимает массив значений, которые будут использованы как аргументы. Подробнее о методах call() и apply() рассказывается в разделе 8.7.3.

содержание 8.3. Аргументы и параметры функций содержание

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

содержание 8.3.1. Необязательные аргументы

Когда число аргументов в вызове функции меньше числа объявленных параметров, недостающие аргументы получают значение undefined. Часто бывает удобным писать функции так, чтобы некоторые аргументы были необязательными и могли опускаться при вызове функции. В этом случае желательно предусмотреть возможность присваивания достаточно разумных значений по умолчанию параметрам, которые могут быть опущены. Например:

Вместо инструкции if в первой строке этой функции можно использовать оператор || следующим образом:

a = a || [];

В разделе 4.10.2 говорилось, что оператор || возвращает первый аргумент, если он имеет истинное значение, и в противном случае возвращает второй аргумент. В данном примере, если во втором аргументе будет передан какой-либо объект, функция будет использовать его. Но если второй аргумент отсутствует (или в нем будет передано значение null), будет использоваться вновь созданный массив.

Обратите внимание, что при объявлении функций необязательные аргументы должны завершать список аргументов, чтобы их можно было опустить. Программист, который будет писать обращение к вашей функции, не сможет передать второй аргумент и при этом опустить первый: он будет вынужден явно передать в первом аргументе значение undefined. Обратите также внимание на комментарий /* необязательный */ в определении функции, который подчеркивает тот факт, что параметр является необязательным.

содержание 8.3.2. Списки аргументов переменной длины: объект Arguments

Если число аргументов в вызове функции превышает число имен параметров, функция лишается возможности напрямую обращаться к неименованным значениям. Решение этой проблемы предоставляет объект Arguments. В теле функции идентификатор arguments ссылается на объект Arguments, присутствующий в вызове. Объект Arguments – это объект, подобный массиву (раздел 7.11), позволяющий извлекать переданные функции значения по их номерам, а не по именам.

Предположим, что была определена функция f, которая требует один аргумент, x. Если вызвать эту функцию с двумя аргументами, то первый будет доступен внутри функции по имени параметра х или как arguments[0]. Второй аргумент будет доступен только как arguments[1]. Кроме того, подобно настоящим массивам, arguments имеет свойство length, определяющее количество содержащихся элементов. То есть в теле функции f, вызываемой с двумя аргументами, arguments.length имеет значение 2.

Объект Arguments может использоваться с самыми разными целями. Следующий пример показывает, как с его помощью проверить, была ли функция вызвана с правильным числом аргументов, – ведь JavaScript этого за вас не сделает:

Обратите внимание, что зачастую нет необходимости проверять количество аргументов, как в данном примере. Поведение по умолчанию интерпретатора Java-Script отлично подходит для большинства случаев: отсутствующие аргументы замещаются значением undefined, а лишние аргументы просто игнорируются. Объект Arguments иллюстрирует важную возможность JavaScript-функций: они могут быть написаны таким образом, чтобы работать с любым количеством аргументов. Следующая функция принимает любое число аргументов и возвращает значение самого большого из них (аналогично ведет себя встроенная функция Math.max():

Функции, подобные этой и способные принимать произвольное число аргументов, называются функциями с переменным числом аргументов (variadic functions, variable arity functions, или varargs functions). Этот термин возник вместе с появлением языка программирования C.

Обратите внимание, что функции с переменным числом аргументов не должны допускать возможность вызова с пустым списком аргументов. Будет вполне разумным использовать объект arguments[] при написании функции, ожидающей получить фиксированное число обязательных именованных аргументов, за которыми может следовать произвольное число необязательных неименованных аргументов.

Не следует забывать, что arguments фактически не является массивом – это объект Arguments. В каждом объекте Arguments имеются пронумерованные элементы массива и свойство length, но с технической точки зрения это не массив. Лучше рассматривать его как объект, имеющий некоторые пронумерованные свойства. Подробнее об объектах, подобных массивам, рассказывается в разделе 7.11.

У объекта Arguments есть одна очень необычная особенность. Когда у функции имеются именованные параметры, элементы массива объекта Arguments при выполнении в нестрогом режиме являются синонимами параметров, содержащих аргументы функции. Массив arguments[] и имена параметров – это два разных средства обращения к одним и тем же переменным. Изменение значения аргумента через имя аргумента меняет значение, извлекаемое через массив arguments[]. Изменение значения аргумента через массив arguments[] меняет значение, извлекаемое по имени аргумента. Например:

Определенно, это не совсем то поведение, которое можно было бы ожидать от настоящего массива. В этом случае arguments[0] и x могли бы изначально ссылаться на одно и то же значение, но изменение одного не должно оказывать влияния на другое. Эта особенность в поведении объекта Arguments была ликвидирована в строгом режиме, предусматриваемом стандартом ECMAScript 5. Кроме того, в строгом режиме имеется еще несколько отличий. В нестрогом режиме arguments – это всего лишь обычный JavaScript-идентификатор, а не зарезервированное слово. В строгом режиме не допускается использовать имя arguments в качестве имени параметра или локальной переменной функции и отсутствует возможность присваивать значения элементам arguments.

содержание 8.3.2.1. Свойства callee и caller

Помимо элементов своего массива объект Arguments определяет свойства callee и caller (в пер. с англ. "вызывающая программа") . При попытке изменить значения этих свойств в строгом режиме ECMAScript 5 гарантированно возбуждается исключение TypeError. Однако в нестрогом режиме стандарт ECMAScript утверждает, что свойство callee ссылается на выполняемую в данный момент функцию. Свойство caller не является стандартным, но оно присутствует во многих реализациях и ссылается на функцию, вызвавшую текущую. Свойство caller можно использовать для доступа к стеку вызовов, а свойство callee особенно удобно использовать для рекурсивного вызова неименованных функций:

содержание 8.3.3. Использование свойств объекта в качестве аргументов

Когда функция имеет более трех параметров, становится трудно запоминать правильный порядок их следования. Чтобы предотвратить ошибки и избавить программиста от необходимости заглядывать в документацию всякий раз, когда он намеревается вставить в программу вызов такой функции, можно предусмотреть возможность передачи аргументов в виде пар имя/значение в произвольном порядке. Чтобы реализовать такую возможность, при определении функции следует предусмотреть передачу объекта в качестве единственного аргумента. Благодаря такому стилю пользователи функции смогут передавать функции объект, в котором будут определяться необходимые пары имя/значение. В следующем фрагменте приводится пример такой функции, а также демонстрируется возможность определения значений по умолчанию для опущенных аргументов:

содержание 8.3.4. Типы аргументов

В языке JavaScript параметры функций объявляются без указания их типов, а во время передачи значений функциям не производится никакой проверки их типов. Вы можете сделать свой программный код самодокументируемым, выбирая описательные имена для параметров функций и включая описание типов аргументов в комментарии, как это сделано в только что рассмотренном примере функции arraycopy(). Для необязательных аргументов в комментарий можно добавлять слово «необязательный» («optional»). А если функция может принимать произвольное число аргументов, можно использовать многоточие:

function max(/* number... */) { /* code here */ }

Как отмечалось в разделе 3.8, при необходимости JavaScript выполняет преобразование типов. Таким образом, если определить функцию, которая ожидает получить строковый аргумент, а затем вызвать ее с аргументом какого-нибудь другого типа, значение аргумента просто будет преобразовано в строку, когда функция пытается обратиться к нему как к строке. В строку может быть преобразовано любое простое значение, и все объекты имеют методы toString() (правда, не всегда полезные); тем самым устраняется вероятность появления ошибки.

Однако такой подход может использоваться не всегда. Вернемся к методу arraycopy(), продемонстрированному выше. Он ожидает получить массив в первом аргументе. Любое обращение к функции окажется неудачным, если первым аргументом будет не массив (или, возможно, объект, подобный массиву). Если функция должна вызываться чаще, чем один-два раза, следует добавить в нее проверку соответствия типов аргументов. Гораздо лучше сразу же прервать вызов функции в случае передачи аргументов ошибочных типов, чем продолжать выполнение, которое потерпит неудачу с сообщением об ошибке, запутывающим ситуацию. Ниже приводится пример функции, выполняющей проверку типов. Обратите внимание, что она использует функцию isArrayLike() из раздела 7.11:

Приведенный в этом примере метод sum() весьма строго относится к проверке типов входных аргументов и генерирует исключения с достаточно информативными сообщениями, если типы входных аргументов не соответствуют ожидаемым. Тем не менее он остается достаточно гибким, обслуживая наряду с настоящими массивами объекты, подобные массивам, и игнорируя элементы, имеющие значения null и undefined.

JavaScript - чрезвычайно гибкий и к тому же слабо типизированный язык, благодаря чему можно писать функции, которые достаточно терпимо относятся к количеству и типам входных аргументов. Следующий пример – метод flexisum() – реализует такой подход (и, вероятно, является примером другой крайности). Например, он принимает любое число входных аргументов и рекурсивно обрабатывает те из них, которые являются массивами. Вследствие этого он может принимать переменное число аргументов или массив аргументов. Кроме того, он прилагает максимум усилий, чтобы преобразовать нечисловые аргументы в числа, прежде чем сгенерировать исключение:

содержание 8.4. Функции как данные содержание

Важнейшими особенностями функций являются возможности их определять и вызывать. Определение и вызов функции – это синтаксические средства JavaScript и большинства других языков программирования. Однако в JavaScript функции - это не только синтаксические конструкции, но и значения, а это означает, что они могут присваиваться переменным, храниться в свойствах объектов или элементах массивов, передаваться в качестве аргументов функциям и т. д.

(Это может показаться не столь интересным, если вы не знакомы с такими языками, как Java, в которых функции являются частью программы, но не могут управляться программой.)

Чтобы понять, как функции в JavaScript могут быть одновременно синтаксическими конструкциями и данными, рассмотрим следующее определение функции:

function square(x) { return x*x; }

Это определение создает новый объект Function и присваивает его переменной square. Имя функции на самом деле нематериально - это просто имя переменной, которая ссылается на объект Function. Функция может быть присвоена другой переменной и при этом работать так же:

Функции могут быть присвоены не только глобальным переменным, но и свойствам объектов. В этом случае их называют методами:

Функции могут быть даже безымянными, например, в случае присваивания их элементам массива:

Синтаксис вызова функции в последнем примере выглядит необычно, однако это вполне допустимый вариант применения выражения вызова!

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

Пример 8.2. Использование функций как данных

В качестве еще одного примера использования функций как значений рассмотрим метод Array.sort(). Он сортирует элементы массива. Существует много возможных порядков сортировки (числовой, алфавитный, по датам, по возрастанию, по убыванию и т. д.), поэтому метод sort() принимает в качестве необязательного аргумента функцию, которая сообщает о том, как выполнять сортировку. Эта функция делает простую работу: получает два значения, сравнивает их и возвращает результат, указывающий, какой из элементов должен быть впереди. Она делает метод Array.sort() совершенно универсальным и бесконечно гибким – с ее использованием метод Array.sort() может сортировать любой тип данных в любом мыслимом порядке. Примеры использования функции Array.sort() представлены в разделе 7.8.3.

содержание 8.4.1. Определение собственных свойств функций

Функции в языке JavaScript являются не простыми значениями, а особой разновидностью объектов, поэтому функции могут иметь свойства. Когда функции требуется «статическая» переменная, значение которой должно сохраняться между ее вызовами, часто оказывается удобным использовать свойство объекта Function, позволяющее не занимать пространство имен определениями глобальных переменных. Пусть нужно написать функцию, возвращающую уникальное целое число при каждом своем вызове. Функция никогда не должна возвращать одно и то же значение дважды. Чтобы обеспечить это, функция должна запоминать последнее возвращенное значение и сохранять его между ее вызовами. Можно было бы хранить последнее возвращенное значение в глобальной переменной, но это нежелательно, потому что оно используется только самой функцией. Лучше сохранять его в свойстве объекта Function. Вот пример функции, возвращающей уникальное целое значение при каждом вызове:

Еще один пример, взгляните на следующую функцию factorial(), которая использует собственные свойства (интерпретируя себя как массив) для сохранения результатов предыдущих вычислений:

содержание 8.5. Функции как пространства имен содержание

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

Пусть, например, у вас есть модуль JavaScript, который вы хотите использовать в различных JavaScript-программах (или, если говорить на языке клиентского JavaScript, на различных веб-страницах). Допустим, что в программном коде этого модуля, как и в практически любом другом программном коде, объявляются переменные для хранения промежуточных результатов вычислений. Проблема состоит в том, что модуль будет использоваться множеством различных программ, и поэтому заранее неизвестно, будут ли возникать конфликты между переменными, создаваемыми модулем, и переменными, используемыми в программах, импортирующих этот модуль. Решение состоит в том, чтобы поместить программный код модуля в функцию, и затем вызывать эту функцию. При таком подходе переменные модуля из глобальных превратятся в локальные переменные функции:

Данный программный код объявляет единственную глобальную переменную: имя функции «mymodule». Если даже единственное имя – это слишком много, можно определить и вызвать анонимную функцию в одном выражении:

Такой способ определения и вызова функции в одном выражении настолько часто используется в практике, что превратился в типичный прием. Обратите внимание на использование в этом примере круглых скобок. Открывающая скобка перед ключевым словом function является обязательной, потому что без нее ключевое слово function будет интерпретироваться как инструкция объявления функции. Скобки позволят интерпретатору распознать этот фрагмент как выражение определения функции. Это обычный способ – заключение в скобки, даже когда они не требуются, определения функции, которая должна быть вызвана сразу же после ее определения.

Практическое применение приема создания пространства имен демонстрируется в примере 8.3. Здесь определяется анонимная функция, возвращающая функцию extend(), подобную той, что была представлена в примере 6.2. Анонимная функция проверяет наличие хорошо известной ошибки в Internet Explorer и возвращает исправленную версию функции, если это необходимо. Помимо этого, анонимная функция играет роль пространства имен, скрывающего массив с именами свойств.

Пример 8.3. Функция extend(), исправленная, если это необходимо

содержание 8.6. Замыкания содержание

Как и в большинстве языков программирования, в JavaScript используются лексические области видимости. Это означает, что при выполнении функций действуют области видимости переменных, которые имелись на момент их определения, а не на момент вызова. Для реализации лексической области видимости внутренняя информация о состоянии объекта функции в языке JavaScript должна включать не только программный код функции, но еще и ссылку на текущую цепочку областей видимости. (Прежде чем продолжить чтение этого раздела, вам, возможно, следует повторно прочитать сведения об областях видимости переменных и цепочках областей видимости в разделах 3.10 и 3.10.3) Такая комбинация объекта функции и области видимости (множества связанных переменных), в которой находятся переменные, используемые вызываемой функцией, в литературе по информационным технологиям называется замыканием.

(Это старый термин, который отражает тот факт, что переменные функции привязаны к цепочке областей видимости, вследствие чего функция образует «замыкание» по своим переменным.)

Технически все функции в языке JavaScript образуют замыкания: они являются объектами и имеют ассоциированные с ними цепочки областей видимости. Большинство функций вызываются внутри той же цепочки областей видимости, которая действовала на момент определения функции, и в этой ситуации факт образования замыкания не имеет никакого значения. Интересные особенности замыканий начинают проявляться, когда их вызов производится в другой цепочке областей видимости, отличной от той, что действовала на момент определения. Чаще всего это происходит, когда вложенный объект– функция возвращается функцией, вмещающей ее определение. . Существует множество мощных приемов программирования, вовлекающих такого рода вложенные функции-замыкания, и их использование довольно широко распространено в программировании на языке JavaScript. Замыкания могут выглядеть малопонятными при первом знакомстве, однако вам необходимо хорошо понимать их, чтобы чувствовать себя уверенно при их использовании.

Первый шаг к пониманию замыканий - знакомство с лексическими правилами области видимости, действующими для вложенных функций. Взгляните на следующий пример (который напоминает пример в разделе 3.10):

Функция checkscope() объявляет локальную переменную и вызывает функцию, возвращающую значение этой переменной. Должно быть совершенно понятно, почему вызов checkscope() возвращает строку «local scope». Теперь немного изменим пример. Сможете ли вы сказать, какое значение вернет этот фрагмент?

В этой версии пара круглых скобок была перемещена из тела функции checkscope() за ее пределы. Вместо вызова вложенной функции и возврата ее результата checkscope() теперь просто возвращает сам объект вложенной функции. Что произойдет, если вызвать вложенную функцию (добавив вторую пару скобок в последней строке примера) из-за пределов функции, в которой она определена?

Напомню главное правило лексической области видимости: при выполнении функции в языке JavaScript используется цепочка областей видимости, действовавшая на момент ее определения. Вложенная функция f() была определена в цепочке видимости, где переменная scope связана со значением «local scope». Эта связь остается действовать и при выполнении функции f, независимо от того, откуда был произведен ее вызов. Поэтому последняя строка в примере выше вернет «local scope», а не «global scope». Проще говоря, эта особенность является самой удивительной и мощной чертой замыканий: они сохраняют связь с локальными переменными (и параметрами) внешней функции, где они были определены.

Реализация замыканий

Понять суть замыканий будет совсем несложно, если усвоить правило лексической области видимости: во время выполнения функции используется цепочка областей видимости, которая действовала в момент ее определения. Однако некоторые программисты испытывают сложности при освоении замыканий, потому что не до конца понимают особенности реализации. Известно, думают они, что локальные переменные, объявленные во внешней функции, прекращают свое существование после выхода из внешней функции, но тогда как вложенная функция может использовать цепочку областей видимости, которая больше не существует? Если вы задавали себе такой вопрос, значит, у вас наверняка есть опыт работы с низкоуровневыми языками программирования, такими как C, и аппаратными архитектурами, использующими стек: если локальные переменные размещать на стеке, они действительно прекращают свое существование после завершения функции.

Но вспомните определение цепочки областей видимости из раздела 3.10.3. Там она описывалась как список объектов, а не стек. Каждый раз, когда интерпретатор JavaScript вызывает функцию, он создает новый объект для хранения локальных переменных этой функции, и этот объект добавляется в цепочку областей видимости. Когда функция возвращает управление, этот объект удаляется из цепочки. Если в программе нет вложенных функций и нет ссылок на этот объект, он будет утилизирован сборщиком мусора. Если в программе имеются вложенные функции, тогда каждая из этих функций будет владеть ссылкой на свою цепочку областей видимости, а цепочка будет ссылаться на объекты с локальными переменными. Если объекты вложенных функций существуют только в пределах своих внешних функций, они сами будут утилизированы сборщиком мусора, а вместе с ними будут утилизированы и объекты с локальными переменными, на которые они ссылались. Но если функция определяет вложенную функцию и возвращает ее или сохраняет в свойстве какого-либо объекта, то образуется внешняя ссылка на вложенную функцию. Такой объект вложенной функции не будет утилизирован сборщиком мусора, и точно так же не будет утилизирован объект с локальными переменными, на который она ссылается.

В разделе 8.4.1 был приведен пример функции uniqueInteger(), в которой используется свойство самой функции для сохранения следующего возвращаемого значения. Недостаток такого решения состоит в том, что ошибочный или злонамеренный программный код может сбросить значение счетчика или записать в него нечисловое значение, вынудив функцию uniqueInteger() нарушить обязательство возвращать «уникальное» или «целочисленное» значение:

Замыкания запирают локальные переменные в объекте вызова функции и могут использовать эти переменные для хранения частной информации. Ниже показано, как можно реализовать функцию uniqueInteger() с использованием замыкания:

(Сравним действие функции с предыдущим примером.)

Внимательно изучите этот пример, чтобы понять, как он действует. На первый взгляд код начинается со строки с инструкцией присваивания функции переменной uniqueInteger. Фактически же это определение и вызов функции (как подсказывает открывающая круглая скобка в первой строке), поэтому в действительности переменной uniqueInteger присваивается значение, возвращаемое функцией. Если теперь обратить внимание на тело функции, можно увидеть, что она возвращает другую функцию. Именно этот объект вложенной функции и присваивается переменной uniqueInteger. Вложенная функция имеет доступ к переменным в ее области видимости и может использовать переменную counter, объявленную во внешней функции. После возврата из внешней функции никакой другой программный код не будет иметь доступа к переменной counter: вложенная функция будет обладать исключительным правом доступа к ней.

Скрытые переменные, такие как counter, не являются исключительной собственностью единственного замыкания: в одной и той же внешней функции вполне возможно определить две или более вложенных функций, которые будут совместно использовать одну цепочку областей видимости. Рассмотрим следующий пример:

Функция counter() возвращает объект «счетчик». Этот объект имеет два метода: count(), возвращающий следующее целое число, и reset(), сбрасывающий счетчик в начальное состояние. В первую очередь следует понимать, что два метода совместно используют одну и ту же приватную переменную n. Во-вторых, каждый вызов функции counter() создает новую цепочку областей видимости и новую скрытую переменную. То есть если вызвать функцию counter() дважды, она вернет два объекта-счетчика (c и d) с различными скрытыми переменными. Вызов методов count() и reset() одного объекта-счетчика не оказывает влияния на другой.

Важно отметить, что описанный прием образования замыканий можно использовать в комбинации с приемом определения свойств с методами доступа. Следующая версия функции counter() является вариацией примера, представленного в разделе 6.6, но здесь для приватной информации вместо обычного свойства объекта используются замыкания:

Обратите внимание, что эта версия функции counter() не объявляет локальную переменную. Для сохранения информации она просто использует параметр n, доступный обоим методам доступа к свойству. Это позволяет программе, вызывающей counter(), определять начальное значение скрытой переменной.

В примере 8.4 демонстрируется обобщение приема совместного использования скрытой информации в замыканиях. Этот пример определяет функцию addPrivateProperty(), которая в свою очередь определяет скрытую переменную и две вложенные функции для чтения и записи значения этой переменной. Она добавляет эти вложенные функции как методы указанного вами объекта:

Мы увидели несколько примеров, когда замыкания определяются в одной и той же цепочке областей видимости и совместно используют одну локальную переменную или переменные. Важно знать и уметь пользоваться этим приемом, но не менее важно уметь распознавать ситуации, когда замыкания получают переменную в совместное использование по ошибке. Рассмотрим следующий пример:

При создании подобного программного кода, который создает множество замыканий в цикле, часто допускают ошибку, помещая цикл внутрь функции, которая определяет замыкания. Например, взгляните на следующий фрагмент:

Функция в приведенном коде создает 10 замыканий и сохраняет их в массиве. Замыкания образуются в одном и том же вызове функции, поэтому все они получат доступ к переменной i. Когда constfuncs() вернет управление, переменная i будет иметь значение 10, и все 10 замыканий будут совместно использовать это значение. Таким образом, все функции в возвращаемом массиве будут возвращать одно и то же значение, что совсем не то, чего мы пытались добиться. Важно помнить, что цепочка областей видимости, связанная с замыканием, "живет". Вложенные функции не создают приватные копии области видимости и не фиксируют значения переменных.

Кроме того, при создании замыканий следует помнить, что this - это ключевое слово, а не переменная. Как отмечалось выше, каждый вызов функции получает значение this, и замыкание не имеет доступа к значению this внешней функции, если внешняя функция не сохранит его в переменной:

То же относится и к объекту arguments. Это не ключевое слово, но он автоматически объявляется при каждом вызове функции. Поскольку замыкания при вызове получают собственный объект arguments, они не могут обращаться к массиву аргументов внешней функции, если внешняя функция не сохранит этот массив в переменной с другим именем:

Далее в этой главе в примере 8.5 определяется замыкание, использующее эти приемы для получения доступа к значениям this и arguments внешней функции.

содержание 8.7. Свойства и методы функций и конструктор Function содержание

Мы видели, что в JavaScript-программах функции могут использоваться как значения. Оператор typeof возвращает для функций строку «function», однако в действительности функции в языке JavaScript – это особого рода объекты. А раз функции являются объектами, то они имеют свойства и методы, как любые другие объекты. Существует даже конструктор Function(), который создает новые объекты функций. В следующих подразделах описываются свойства и методы функций, а также конструктор Function(). Кроме того, информация обо всем этом приводится в справочном разделе.

содержание 8.7.1. Свойство length

В теле функции свойство arguments.length определяет количество аргументов, переданных функции. Однако свойство length самой функции имеет иной смысл. Это свойство, доступное только для чтения, возвращает число параметров функции – число аргументов, указанное при объявлении функции, которое обычно представляет собой число аргументов, которое функция ожидает получить.

В следующем фрагменте определяется функция с именем check(), получающая массив аргументов arguments от другой функции. Она сравнивает свойство arguments.length (число фактически переданных аргументов) со свойством argumentsarguments.callee.length, (число ожидаемых аргументов), чтобы определить, передано ли функции столько аргументов, сколько она ожидает. Если значения не совпадают, генерируется исключение. За функцией check() следует тестовая функция f(), демонстрирующая порядок использования функции check():

содержание 8.7.2. Свойство prototype

Любая функция имеет свойство prototype, ссылающееся на объект, известный как Object.prototype. Каждая функция имеет свой Object.prototype. Когда функция используется в роли конструктора, вновь созданный объект наследует свойства прототипа. Прототипы и свойство prototype обсуждались в разделе 6.1.3, и мы еще раз вернемся к этим понятиям в главе 9.

содержание 8.7.3. Методы call() и apply()

Методы call() и apply() позволяют выполнять косвенный вызов функции (раздел 8.2.4), как если бы она была методом некоторого другого объекта. (Мы уже использовали метод call() в примере 6.4 для вызова Object.prototype.toString относительно объекта, класс которого необходимо было определить.) Первым аргументом обоим методам, и call(), и apply(), передается объект, чьим методом как бы становится вызываемая функция; этот аргумент определяет контекст вызова и становится значением ключевого слова this в теле функции. Чтобы вызвать функцию f() (без аргументов) как метод объекта о, можно использовать любой из методов call() или apply():

f.call(o);
f.apply(o);

Любая из этих строк кода эквивалентна следующему фрагменту (где предполагается, что объект o не имеет свойства с именем m):

o.m = f; // Временно сделать f методом o.
o.m(); // Вызывать его без аргументов.
delete o.m; // Удалить временный метод.

В строгом режиме ECMAScript 5 первый аргумент методов call() и apply() становится значением this, даже если это простое значение, null или undefined. В ECMAScript 3 и в нестрогом режиме значения null и undefined замещаются глобальным объектом, а простое значение – соответствующим объектом-оберткой.

Все остальные аргументы метода call(), следующие за первым аргументом, определяющим контекст вызова, передаются вызываемой функции. Например, ниже показано, как можно передать функции f() два числа и вызвать ее, как если бы она была методом объекта o:

f.call(o, 1, 2);

Метод apply() действует подобно методу call() , за исключением того, что аргументы для функции передаются в виде массива:

f.apply(o, [1,2]);

Если функция способна обрабатывать произвольное число аргументов, метод apply() может использоваться для вызова такой функции в контексте массива произвольной длины. Например, чтобы отыскать наибольшее число в массиве чисел, для передачи элементов массива функции Math.max() можно было бы использовать метод apply():

var biggest = Math.max.apply(Math, array_of_numbers);

Обратите внимание, что метод apply() может работать не только с настоящими массивами, но и с объектами, подобными массивам. В частности, вы можете вызвать функцию с теми же аргументами, что и текущую функцию, передав массив с аргументами непосредственно методу apply(). Этот прием демонстрируется ниже:

// Замещает метод m объекта o версией метода, которая регистрирует
// сообщения до и после вызова оригинального метода.

Эта функция trace() принимает объект и имя метода. Она замещает указанный метод новым методом, который «обертывает» оригинальный метод дополнительной функциональностью. Такой прием динамического изменения существующих методов иногда называется «обезьяньей заплатой» (“monkey-patching”).

содержание 8.7.4. Метод bind()

Метод bind() впервые появился в ECMAScript 5, но его легко имитировать в ECMAScript 3. Как следует из его имени, основное назначение метода bind() состоит в том, чтобы связать (bind) функцию с объектом. Если вызвать метод bind() функции f и передать ему объект o, он вернет новую функцию. Вызов новой функции (как обычной функции) выполнит вызов оригинальной функции f как метода объекта o. Любые аргументы, переданные новой функции, будут переданы оригинальной функции. Например:

Такой способ связывания легко реализовать в ECMAScript 3, как показано ниже:

// Возвращает функцию, которая вызывает f как метод объекта o
// и передает ей все свои аргументы.

Метод bind() в ECMAScript 5 не просто связывает функцию с объектом. Он также выполняет частичное применение: помимо значения this связаны будут все аргументы, переданные методу bind() после первого его аргумента. Частичное применение – распространенный прием в функциональном программировании и иногда называется каррингом (currying). Ниже приводится несколько примеров использования метода bind() для частичного применения:

В ECMAScript 3 также возможно связывать значение this и выполнять частичное применение. Стандартный метод bind() можно имитировать программным кодом, который приводится в примере 8.5. Обратите внимание, что этот метод сохраняется как Function.prototype.bind(), благодаря чему все функции наследуют его. Данный прием подробно рассматривается в разделе 9.4.

Пример 8.5. Метод Function.bindf) для ECMAScript 3

Обратите внимание, что функция, возвращаемая этим методом bind(), является замыканием, использующим объявленные во внешней функции переменные self и boundArgs, которые остаются доступными вложенной функции даже хотя внутренняя функция возвращена из внешней функции и вызывается после того, как внешняя функция завершена.

Метод bind(), определяемый стандартом ECMAScript 5, имеет некоторые особенности, которые невозможно реализовать в ECMAScript 3. Прежде всего, настоящий метод bind() возвращает объект function, свойство length которого установлено в соответствии с количеством параметров связываемой функции минус количество связанных аргументов (но не меньше нуля). Во-вторых, метод bind() в ECMAScript 5 может использоваться для частичного применения функций-конструкторов. Если функцию, возвращаемую методом bind(), использовать как конструктор, значение this , переданное методу bind(), игнорируется, и оригинальная функция будет вызвана как конструктор с уже связанными аргументами, если они были определены. Функции, возвращаемые методом bind(), не имеют свойства prototype (свойство prototype обычных функций нельзя удалить), и объекты, созданные связанными функциями-конструкторами, наследуют свойство prototype оригинального, несвязанного конструктора. Кроме того, для целей оператора instanceof связанные конструкторы действуют точно так же, как несвязанные.

содержание 8.7.5. Метод toString()

Подобно другим объектам в языке JavaScript, функции имеют метод toString(). Спецификация ECMAScript требует, чтобы этот метод возвращал строку, следующую синтаксису инструкции объявления функции. На практике большинство реализаций (но не все) метода toString() возвращают полный исходный текст функции. Для встроенных функций обычно возвращается строка, содержащая вместо тела функции текст «[native code]» или аналогичный.

содержание 8.7.6. Конструктор Function()

Функции обычно определяются с помощью ключевого слова function либо в форме инструкции объявления функции, либо в форме выражения-литерала. Однако функции могут также определяться с помощью конструктора Function(). Например:

var f = new Function("x", "y", "return x*y;");

Эта строка создаст новую функцию, которая более или менее эквивалентна функции, объявленной с помощью более привычного синтаксиса:

var f = function(x, y) { return x*y; }

Конструктор Function() принимает произвольное число строковых аргументов. Последний аргумент должен содержать текст с телом функции; он может включать произвольное число инструкций на языке JavaScript, разделенных точкой с запятой. Все остальные аргументы конструктора интерпретируются как имена параметров функции. Чтобы создать функцию, не имеющую аргументов, достаточно передать конструктору всего одну строку – тело функции.

Примечательно, что конструктору Function() не передается никаких аргументов, определяющих имя создаваемой функции. Подобно литералам функций, конструктор Function() создает анонимные функции.

Есть несколько моментов, связанных с конструктором Function(), о которых следует упомянуть особо:

• Конструктор Function() позволяет динамически создавать и компилировать функции в процессе выполнения программы.

• При каждом вызове конструктор Function() выполняет синтаксический анализ тела функции и создает новый объект функции. Если вызов конструктора производится в теле цикла или часто вызываемой функции, это может отрицательно сказаться на производительности программы. Напротив, вложенные функции и выражения определения функций внутри циклов не компилируются повторно.

• И последний, очень важный момент: когда функция создается с помощью конструктора Function(), не учитывается лексическая область видимости – функции всегда компилируются как глобальные функции, что наглядно демонстрирует следующий фрагмент:

Точнее всего конструктор Function() соответствует глобальной версии eval() (раздел 4.12.2), которая определяет новые переменные и функции в своей собственной области видимости. Вам редко придется использовать этот конструктор в своих программах.

содержание 8.7.7. Вызываемые объекты

В разделе 7.11 мы узнали, что существуют объекты, «подобные массивам», которые не являются настоящими массивами, но во многих случаях могут интерпретироваться как массивы. Аналогичная ситуация складывается с функциями. Вызываемый объект – это любой объект, который может быть вызван в выражении вызова функции. Все функции являются вызываемыми объектами, но не все вызываемые объекты являются функциями.

Вызываемые объекты, не являющиеся функциями, встречаются в современных реализациях JavaScript в двух ситуациях. Во-первых, веб-броузер IE (версии 8 и ниже) реализует клиентские методы, такие как Window.alert() и Document.getElementsById(), используя вызываемые объекты, а не объекты класса Function(). Эти методы действуют в IE точно так же, как в других браузерах, но они не являются объектами Function(). В IE9 был выполнен переход на использование настоящих функций, поэтому со временем эта разновидность вызываемых объектов будет использоваться все меньше и меньше.

Другой типичной разновидностью вызываемых объектов являются объекты RegExp – во многих браузерах предоставляется возможность напрямую вызывать объект RegExp, как более краткий способ вызова его метода exec(). Эта возможность не предусматривается стандартом JavaScript. В свое время она была реализована компанией Netscape и подхвачена другими производителями для обеспечения совместимости. Старайтесь не писать программы, опирающиеся на возможность вызова объектов RegExp: данная особенность, скорее всего, будет объявлена нерекомендуемой и будет ликвидирована в будущем. Оператор typeof не во всех браузерах одинаково распознает вызываемые объекты RegExp. В одних браузерах он возвращает строку «function», а в других – «object».

Если в программе потребуется определить, является ли объект настоящим объектом function (и обладает методами функций), сделать это можно, определив значение атрибута class (раздел 6.8.2), использовав прием, продемонстрированный в примере 6.4:

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

содержание 8.8. Функциональное программирование содержание

JavaScript не является языком функционального программирования, таким как Lisp или Haskell, но тот факт, что программы на языке JavaScript могут манипулировать функциями как объектами означает, что в JavaScript можно использовать приемы функционального программирования. Масса методов в ECMAScript 5, таких как map() и reduce(), сами по себе способствуют использованию функционального стиля программирования. В следующих разделах демонстрируются приемы функционального программирования на языке JavaScript. Их цель – не подтолкнуть вас к использованию этого замечательного стиля программирования, а показать широту возможностей функций в языке JavaScript.

()Если эта тема вам любопытна, вероятно, вас заинтересует возможность использования (или хотя бы знакомства) библиотеки Functional JavaScript Оливера Стила (Oliver Steel), которую можно найти по адресу: http://osteele.com/sources/javascript/functional/.

содержание 8.8.1. Обработка массивов с помощью функций

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

Те же вычисления можно выполнить в более кратком функциональном стиле, задействовав методы массивов map() и reduce(), как показано ниже (краткое описание этих методов приводится в разделе 7.9):

А как быть, если в нашем распоряжении имеется только реализация БСМА- Script 3, где отсутствуют эти новейшие методы массивов? Можно определить собственные функции map() и reduce(), которые будут использовать встроенные методы при их наличии:

После определения этих функций map() и reduce() вычисление среднего и стандартного отклонения будет выглядеть так:

содержание 8.8.2. Функции высшего порядка

Функции высшего порядка - это функции, которые оперируют функциями, принимая одну или более функций и возвращая новую функцию. Например:

Функция not() в приведенном примере является функцией высшего порядка, потому что она принимает функцию в виде аргумента и возвращает новую функцию. В качестве еще одного примера рассмотрим функцию mapper(), представленную ниже. Она принимает функцию в виде аргумента и возвращает новую функцию, которая отображает один массив в другой, применяя указанную функцию. Данная функция использует функцию map(), которая была определена ранее, и важно понимать, чем отличаются эти две функции:

Ниже приводится пример еще одной, более универсальной функции, которая принимает две функции, f и g, и возвращает новую функцию, которая возвращает результат f(g()):

Функции partial() и memoize(), которые определяются в следующем разделе, представляют собой еще две важные функции высшего порядка.

содержание 8.8.3. Частичное применение функций

Метод bind() функции f(раздел 8.7.4) возвращает новую функцию, которая вызывает f в указанном контексте и с заданным набором аргументов. Можно сказать, что он связывает функцию с объектом контекста и частично применяет аргументы. Метод bind() применяет аргументы слева, т. е. аргументы, которые передаются методу bind(), помещаются в начало списка аргументов, передаваемых оригинальной функции. Однако есть возможность частичного применения аргументов справа:

Эти функции частичного применения позволяют легко объявлять новые функции на основе уже имеющихся функций. Например:

Прием частичного применения становится еще более интересным, когда он используется в комбинации с функциями высшего порядка. Например, ниже демонстрируется еще один способ определения функции not(), представленной выше, за счет совместного использования приемов композиции и частичного применения:

Прием композиции и частичного применения можно также использовать для вычисления среднего значения и стандартного отклонения в крайне функциональном стиле:

содержание 8.8.4. Мемоизация

В разделе 8.4.1 была определена функция нахождения факториала, которая сохраняет ранее вычисленные результаты. В функциональном программировании такого рода кэширование называется мемоизацией (memorization). В следующем примере демонстрируется функция memoize() высшего порядка, которая принимает функцию в виде аргумента и возвращает ее мемоизованную версию:

Функция memoize() создает новый объект для использования в качестве кэша и присваивает его локальной переменной, благодаря чему он остается доступным (через замыкание) только для возвращаемой функции. Возвращаемая функция преобразует свой массив arguments в строку и использует ее как имя свойства объекта-кэша. Если значение присутствует в кэше, оно просто возвращается в качестве результата. В противном случае вызывается оригинальная функция, вычисляющая значение для заданной комбинации значений аргументов; полученное значение помещается в кэш и возвращается. Следующий фрагмент демонстрирует, как можно использовать функцию memoize():