Объект функции, NFE
Как мы уже знаем, в JavaScript функция – это значение.
Каждое значение в JavaScript имеет свой тип. А функция – это какой тип?
В JavaScript функции – это объекты.
Можно представить функцию как «объект, который может делать какое-то действие». Функции можно не только вызывать, но и использовать их как обычные объекты: добавлять/удалять свойства, передавать их по ссылке и т.д.
Свойство «name»
Объект функции содержит несколько полезных свойств.
Например, имя функции нам доступно как свойство «name»:
function sayHi() < alert("Hi"); >alert(sayHi.name); // sayHi
Что довольно забавно, логика назначения name весьма умная. Она присваивает корректное имя даже в случае, когда функция создаётся без имени и тут же присваивается, вот так:
let sayHi = function() < alert("Hi"); >; alert(sayHi.name); // sayHi (есть имя!)
Это работает даже в случае присваивания значения по умолчанию:
function f(sayHi = function() <>) < alert(sayHi.name); // sayHi (работает!) >f();
В спецификации это называется «контекстное имя»: если функция не имеет name, то JavaScript пытается определить его из контекста.
Также имена имеют и методы объекта:
let user = < sayHi() < // . >, sayBye: function() < // . >> alert(user.sayHi.name); // sayHi alert(user.sayBye.name); // sayBye
В этом нет никакой магии. Бывает, что корректное имя определить невозможно. В таких случаях свойство name имеет пустое значение. Например:
// функция объявлена внутри массива let arr = [function() <>]; alert( arr[0].name ); // // здесь отсутствует возможность определить имя, поэтому его нет
Впрочем, на практике такое бывает редко, обычно функции имеют name .
Свойство «length»
Ещё одно встроенное свойство «length» содержит количество параметров функции в её объявлении. Например:
function f1(a) <> function f2(a, b) <> function many(a, b, . more) <> alert(f1.length); // 1 alert(f2.length); // 2 alert(many.length); // 2
Как мы видим, троеточие, обозначающее «остаточные параметры», здесь как бы «не считается»
Свойство length иногда используется для интроспекций в функциях, которые работают с другими функциями.
Например, в коде ниже функция ask принимает в качестве параметров вопрос question и произвольное количество функций-обработчиков ответа handler .
Когда пользователь отвечает на вопрос, функция вызывает обработчики. Мы можем передать два типа обработчиков:
- Функцию без аргументов, которая будет вызываться только в случае положительного ответа.
- Функцию с аргументами, которая будет вызываться в обоих случаях и возвращать ответ.
Чтобы вызвать обработчик handler правильно, будем проверять свойство handler.length .
Идея состоит в том, чтобы иметь простой синтаксис обработчика без аргументов для положительных ответов (наиболее распространённый случай), но также и возможность передавать универсальные обработчики:
function ask(question, . handlers) < let isYes = confirm(question); for(let handler of handlers) < if (handler.length == 0) < if (isYes) handler(); >else < handler(isYes); >> > // для положительных ответов вызываются оба типа обработчиков // для отрицательных - только второго типа ask("Вопрос?", () => alert('Вы ответили да'), result => alert(result));
Это частный случай так называемого Ad-hoc-полиморфизма – обработка аргументов в зависимости от их типа или, как в нашем случае – от значения length . Эта идея имеет применение в библиотеках JavaScript.
Пользовательские свойства
Мы также можем добавить свои собственные свойства.
Давайте добавим свойство counter для отслеживания общего количества вызовов:
function sayHi() < alert("Hi"); // давайте посчитаем, сколько вызовов мы сделали sayHi.counter++; >sayHi.counter = 0; // начальное значение sayHi(); // Hi sayHi(); // Hi alert( `Вызвана $ раза` ); // Вызвана 2 раза
Свойство не есть переменная
Свойство функции, назначенное как sayHi.counter = 0 , не объявляет локальную переменную counter внутри неё. Другими словами, свойство counter и переменная let counter – это две независимые вещи.
Мы можем использовать функцию как объект, хранить в ней свойства, но они никак не влияют на её выполнение. Переменные – это не свойства функции и наоборот. Это два параллельных мира.
Иногда свойства функции могут использоваться вместо замыканий. Например, мы можем переписать функцию-счётчик из главы Область видимости переменных, замыкание, используя её свойство:
function makeCounter() < // вместо // let count = 0 function counter() < return counter.count++; >; counter.count = 0; return counter; > let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1
Свойство count теперь хранится прямо в функции, а не в её внешнем лексическом окружении.
Это хуже или лучше, чем использовать замыкание?
Основное отличие в том, что если значение count живёт во внешней переменной, то оно не доступно для внешнего кода. Изменить его могут только вложенные функции. А если оно присвоено как свойство функции, то мы можем его получить:
function makeCounter() < function counter() < return counter.count++; >; counter.count = 0; return counter; > let counter = makeCounter(); counter.count = 10; alert( counter() ); // 10
Поэтому выбор реализации зависит от наших целей.
Named Function Expression
Named Function Expression или NFE – это термин для Function Expression, у которого есть имя.
Например, давайте объявим Function Expression:
let sayHi = function(who) < alert(`Hello, $`); >;
И присвоим ему имя:
let sayHi = function func(who) < alert(`Hello, $`); >;
Чего мы здесь достигли? Какова цель этого дополнительного имени func ?
Для начала заметим, что функция всё ещё задана как Function Expression. Добавление «func» после function не превращает объявление в Function Declaration, потому что оно все ещё является частью выражения присваивания.
Добавление такого имени ничего не ломает.
Функция все ещё доступна как sayHi() :
let sayHi = function func(who) < alert(`Hello, $`); >; sayHi("John"); // Hello, John
Есть две важные особенности имени func , ради которого оно даётся:
- Оно позволяет функции ссылаться на себя же.
- Оно не доступно за пределами функции.
Например, ниже функция sayHi вызывает себя с «Guest» , если не передан параметр who :
let sayHi = function func(who) < if (who) < alert(`Hello, $`); > else < func("Guest"); // использует func, чтобы снова вызвать себя же >>; sayHi(); // Hello, Guest // А вот так - не cработает: func(); // Ошибка, func не определена (недоступна вне функции)
Почему мы используем func ? Почему просто не использовать sayHi для вложенного вызова?
Вообще, обычно мы можем так поступить:
let sayHi = function(who) < if (who) < alert(`Hello, $`); > else < sayHi("Guest"); >>;
Однако, у этого кода есть проблема, которая заключается в том, что значение sayHi может быть изменено. Функция может быть присвоена другой переменной, и тогда код начнёт выдавать ошибки:
let sayHi = function(who) < if (who) < alert(`Hello, $`); > else < sayHi("Guest"); // Ошибка: sayHi не является функцией >>; let welcome = sayHi; sayHi = null; welcome(); // Ошибка, вложенный вызов sayHi больше не работает!
Так происходит, потому что функция берёт sayHi из внешнего лексического окружения. Так как локальная переменная sayHi отсутствует, используется внешняя. И на момент вызова эта внешняя sayHi равна null .
Необязательное имя, которое можно вставить в Function Expression, как раз и призвано решать такого рода проблемы.
Давайте используем его, чтобы исправить наш код:
let sayHi = function func(who) < if (who) < alert(`Hello, $`); > else < func("Guest"); // Теперь всё в порядке >>; let welcome = sayHi; sayHi = null; welcome(); // Hello, Guest (вложенный вызов работает)
Теперь всё работает, потому что имя «func» локальное и находится внутри функции. Теперь оно взято не снаружи (и недоступно оттуда). Спецификация гарантирует, что оно всегда будет ссылаться на текущую функцию.
Внешний код все ещё содержит переменные sayHi и welcome , но теперь func – это «внутреннее имя функции», таким образом она может вызвать себя изнутри.
Это не работает с Function Declaration
Трюк с «внутренним» именем, описанный выше, работает только для Function Expression и не работает для Function Declaration. Для Function Declaration синтаксис не предусматривает возможность объявить дополнительное «внутреннее» имя.
Зачастую, когда нам нужно надёжное «внутреннее» имя, стоит переписать Function Declaration на Named Function Expression.
Итого
Функции – это объекты.
- name – имя функции. Обычно берётся из объявления функции, но если там нет – JavaScript пытается понять его из контекста.
- length – количество аргументов в объявлении функции. Троеточие («остаточные параметры») не считается.
Если функция объявлена как Function Expression (вне основного потока кода) и имеет имя, тогда это называется Named Function Expression (Именованным Функциональным Выражением). Это имя может быть использовано для ссылки на себя же, для рекурсивных вызовов и т.п.
Также функции могут содержать дополнительные свойства. Многие известные JavaScript-библиотеки искусно используют эту возможность.
Они создают «основную» функцию и добавляют множество «вспомогательных» функций внутрь первой. Например, библиотека jQuery создаёт функцию с именем $ . Библиотека lodash создаёт функцию _ , а потом добавляет в неё _.clone , _.keyBy и другие свойства (чтобы узнать о ней побольше см. документацию). Они делают это, чтобы уменьшить засорение глобального пространства имён посредством того, что одна библиотека предоставляет только одну глобальную переменную, уменьшая вероятность конфликта имён.
Таким образом, функция может не только делать что-то сама по себе, но также и предоставлять полезную функциональность через свои свойства.
Задачи
Установка и уменьшение значения счётчика
важность: 5
Измените код makeCounter() так, чтобы счётчик мог уменьшать и устанавливать значение:
- counter() должен возвращать следующее значение (как и раньше).
- counter.set(value) должен устанавливать счётчику значение value .
- counter.decrease() должен уменьшать значение счётчика на 1.
Посмотрите код из песочницы с полным примером использования.
P.S. Для того, чтобы сохранить текущее значение счётчика, можно воспользоваться как замыканием, так и свойством функции. Или сделать два варианта решения: и так, и так.
В решении использована локальная переменная count , а методы сложения записаны прямо в counter . Они разделяют одно и то же лексическое окружение и также имеют доступ к текущей переменной count .
function makeCounter() < let count = 0; function counter() < return count++; >counter.set = value => count = value; counter.decrease = () => count--; return counter; >
Функции
Зачастую нам надо повторять одно и то же действие во многих частях программы.
Например, необходимо красиво вывести сообщение при приветствии посетителя, при выходе посетителя с сайта, ещё где-нибудь.
Чтобы не повторять один и тот же код во многих местах, придуманы функции. Функции являются основными «строительными блоками» программы.
Примеры встроенных функций вы уже видели – это alert(message) , prompt(message, default) и confirm(question) . Но можно создавать и свои.
Объявление функции
Для создания функций мы можем использовать объявление функции.
Пример объявления функции:
function showMessage()
Вначале идёт ключевое слово function , после него имя функции, затем список параметров в круглых скобках через запятую (в вышеприведённом примере он пустой) и, наконец, код функции, также называемый «телом функции», внутри фигурных скобок.
function имя(параметры)
Наша новая функция может быть вызвана по своему имени: showMessage() .
function showMessage() < alert( 'Всем привет!' ); >showMessage(); showMessage();
Вызов showMessage() выполняет код функции. Здесь мы увидим сообщение дважды.
Этот пример явно демонстрирует одно из главных предназначений функций: избавление от дублирования кода.
Если понадобится поменять сообщение или способ его вывода – достаточно изменить его в одном месте: в функции, которая его выводит.
Локальные переменные
Переменные, объявленные внутри функции, видны только внутри этой функции.
function showMessage() < let message = "Привет, я JavaScript!"; // локальная переменная alert( message ); >showMessage(); // Привет, я JavaScript! alert( message ); //
Внешние переменные
У функции есть доступ к внешним переменным, например:
let userName = 'Вася'; function showMessage() < let message = 'Привет, ' + userName; alert(message); >showMessage(); // Привет, Вася
Функция обладает полным доступом к внешним переменным и может изменять их значение.
let userName = 'Вася'; function showMessage() < userName = "Петя"; // (1) изменяем значение внешней переменной let message = 'Привет, ' + userName; alert(message); >alert( userName ); // Вася перед вызовом функции showMessage(); alert( userName ); // Петя, значение внешней переменной было изменено функцией
Внешняя переменная используется, только если внутри функции нет такой локальной.
Если одноимённая переменная объявляется внутри функции, тогда она перекрывает внешнюю. Например, в коде ниже функция использует локальную переменную userName . Внешняя будет проигнорирована:
let userName = 'Вася'; function showMessage() < let userName = "Петя"; // объявляем локальную переменную let message = 'Привет, ' + userName; // Петя alert(message); >// функция создаст и будет использовать свою собственную локальную переменную userName showMessage(); alert( userName ); // Вася, не изменилась, функция не трогала внешнюю переменную
Глобальные переменные
Переменные, объявленные снаружи всех функций, такие как внешняя переменная userName в вышеприведённом коде – называются глобальными.
Глобальные переменные видимы для любой функции (если только их не перекрывают одноимённые локальные переменные).
Желательно сводить использование глобальных переменных к минимуму. В современном коде обычно мало или совсем нет глобальных переменных. Хотя они иногда полезны для хранения важнейших «общепроектовых» данных.
Параметры
Мы можем передать внутрь функции любую информацию, используя параметры.
В нижеприведённом примере функции передаются два параметра: from и text .
function showMessage(from, text) < // параметры: from, text alert(from + ': ' + text); >showMessage('Аня', 'Привет!'); // Аня: Привет! (*) showMessage('Аня', "Как дела?"); // Аня: Как дела? (**)
Когда функция вызывается в строках (*) и (**) , переданные значения копируются в локальные переменные from и text . Затем они используются в теле функции.
Вот ещё один пример: у нас есть переменная from , и мы передаём её функции. Обратите внимание: функция изменяет значение from , но это изменение не видно снаружи. Функция всегда получает только копию значения:
function showMessage(from, text) < from = '*' + from + '*'; // немного украсим "from" alert( from + ': ' + text ); >let from = "Аня"; showMessage(from, "Привет"); // *Аня*: Привет // значение "from" осталось прежним, функция изменила значение локальной переменной alert( from ); // Аня
Значение, передаваемое в качестве параметра функции, также называется аргументом.
- Параметр – это переменная, указанная в круглых скобках в объявлении функции.
- Аргумент – это значение, которое передаётся функции при её вызове.
Мы объявляем функции со списком параметров, затем вызываем их, передавая аргументы.
Рассматривая приведённый выше пример, мы могли бы сказать: "функция showMessage объявляется с двумя параметрами, затем вызывается с двумя аргументами: from и "Привет" ".
Значения по умолчанию
Если при вызове функции аргумент не был указан, то его значением становится undefined .
Например, вышеупомянутая функция showMessage(from, text) может быть вызвана с одним аргументом:
showMessage("Аня");
Это не приведёт к ошибке. Такой вызов выведет "*Аня*: undefined" . В вызове не указан параметр text , поэтому предполагается, что text === undefined .
Если мы хотим задать параметру text значение по умолчанию, мы должны указать его после = :
function showMessage(from, text = "текст не добавлен") < alert( from + ": " + text ); >showMessage("Аня"); // Аня: текст не добавлен
Теперь, если параметр text не указан, его значением будет "текст не добавлен"
В данном случае "текст не добавлен" это строка, но на её месте могло бы быть и более сложное выражение, которое бы вычислялось и присваивалось при отсутствии параметра. Например:
function showMessage(from, text = anotherFunction()) < // anotherFunction() выполнится только если не передан text // результатом будет значение text >
Вычисление параметров по умолчанию
В JavaScript параметры по умолчанию вычисляются каждый раз, когда функция вызывается без соответствующего параметра.
В приведённом выше примере, функция anotherFunction() не будет вызвана вообще, если указан параметр text .
С другой стороны, функция будет независимо вызываться каждый раз, когда text отсутствует.
Использование параметров по умолчанию в ранних версиях JavaScript
Ранние версии JavaScript не поддерживали параметры по умолчанию. Поэтому существуют альтернативные способы, которые могут встречаться в старых скриптах.
Например, явная проверка на undefined :
function showMessage(from, text) < if (text === undefined) < text = 'текст не добавлен'; >alert( from + ": " + text ); >
…Или с помощью оператора || :
function showMessage(from, text) < // Если значение text ложно, тогда присвоить параметру text значение по умолчанию // заметим, что при этом пустая строка text === "" будет также считаться отсутствующим значением text = text || 'текст не добавлен'; . >
Альтернативные параметры по умолчанию
Иногда имеет смысл присваивать значения по умолчанию для параметров не в объявлении функции, а на более позднем этапе.
Во время выполнения функции мы можем проверить, передан ли параметр, сравнив его с undefined :
function showMessage(text) < // . if (text === undefined) < // если параметр отсутствует text = 'пустое сообщение'; >alert(text); > showMessage(); // пустое сообщение
…Или мы можем использовать оператор || :
function showMessage(text) < // если значение text ложно или равняется undefined, тогда присвоить text значение 'пусто' text = text || 'пусто'; . >
Современные движки JavaScript поддерживают оператор нулевого слияния ?? . Его использование будет лучшей практикой, в случае, если большинство ложных значений, таких как 0 , следует расценивать как «нормальные».
function showCount(count) < // если count равен undefined или null, показать "неизвестно" alert(count ?? "неизвестно"); >showCount(0); // 0 showCount(null); // неизвестно showCount(); // неизвестно
Возврат значения
Функция может вернуть результат, который будет передан в вызвавший её код.
Простейшим примером может служить функция сложения двух чисел:
function sum(a, b) < return a + b; >let result = sum(1, 2); alert( result ); // 3
Директива return может находиться в любом месте тела функции. Как только выполнение доходит до этого места, функция останавливается, и значение возвращается в вызвавший её код (присваивается переменной result выше).
Вызовов return может быть несколько, например:
function checkAge(age) < if (age >= 18) < return true; >else < return confirm('А родители разрешили?'); >> let age = prompt('Сколько вам лет?', 18); if ( checkAge(age) ) < alert( 'Доступ получен' ); >else
Возможно использовать return и без значения. Это приведёт к немедленному выходу из функции.
function showMovie(age) < if ( !checkAge(age) ) < return; >alert( "Вам показывается кино" ); // (*) // . >
В коде выше, если checkAge(age) вернёт false , showMovie не выполнит alert .
Результат функции с пустым return или без него – undefined
Если функция не возвращает значения, это всё равно, как если бы она возвращала undefined :
function doNothing() < /* пусто */ >alert( doNothing() === undefined ); // true
Пустой return аналогичен return undefined :
function doNothing() < return; >alert( doNothing() === undefined ); // true
Никогда не добавляйте перевод строки между return и его значением
Для длинного выражения в return может быть заманчиво разместить его на нескольких отдельных строках, например так:
return (some + long + expression + or + whatever * f(a) + f(b))
Код не выполнится, потому что интерпретатор JavaScript подставит точку с запятой после return . Для него это будет выглядеть так:
return; (some + long + expression + or + whatever * f(a) + f(b))
Таким образом, это фактически стало пустым return .
Если мы хотим, чтобы возвращаемое выражение занимало несколько строк, нужно начать его на той же строке, что и return . Или, хотя бы, поставить там открывающую скобку, вот так:
return ( some + long + expression + or + whatever * f(a) + f(b) )
И тогда всё сработает, как задумано.
Выбор имени функции
Функция – это действие. Поэтому имя функции обычно является глаголом. Оно должно быть кратким, точным и описывать действие функции, чтобы программист, который будет читать код, получил верное представление о том, что делает функция.
Как правило, используются глагольные префиксы, обозначающие общий характер действия, после которых следует уточнение. Обычно в командах разработчиков действуют соглашения, касающиеся значений этих префиксов.
Например, функции, начинающиеся с "show" обычно что-то показывают.
Функции, начинающиеся с…
- "get…" – возвращают значение,
- "calc…" – что-то вычисляют,
- "create…" – что-то создают,
- "check…" – что-то проверяют и возвращают логическое значение, и т.д.
Примеры таких имён:
showMessage(..) // показывает сообщение getAge(..) // возвращает возраст (получая его каким-то образом) calcSum(..) // вычисляет сумму и возвращает результат createForm(..) // создаёт форму (и обычно возвращает её) checkPermission(..) // проверяет доступ, возвращая true/false
Благодаря префиксам, при первом взгляде на имя функции становится понятным, что делает её код, и какое значение она может возвращать.
Одна функция – одно действие
Функция должна делать только то, что явно подразумевается её названием. И это должно быть одним действием.
Два независимых действия обычно подразумевают две функции, даже если предполагается, что они будут вызываться вместе (в этом случае мы можем создать третью функцию, которая будет их вызывать).
Несколько примеров, которые нарушают это правило:
- getAge – будет плохим выбором, если функция будет выводить alert с возрастом (должна только возвращать его).
- createForm – будет плохим выбором, если функция будет изменять документ, добавляя форму в него (должна только создавать форму и возвращать её).
- checkPermission – будет плохим выбором, если функция будет отображать сообщение с текстом доступ разрешён/запрещён (должна только выполнять проверку и возвращать её результат).
В этих примерах использовались общепринятые смыслы префиксов. Конечно, вы в команде можете договориться о других значениях, но обычно они мало отличаются от общепринятых. В любом случае вы и ваша команда должны чётко понимать, что значит префикс, что функция с ним может делать, а чего не может.
Сверхкороткие имена функций
Имена функций, которые используются очень часто, иногда делают сверхкороткими.
Например, фреймворк jQuery определяет функцию с помощью $ . В библиотеке Lodash основная функция представлена именем _ .
Это исключения. В основном имена функций должны быть в меру краткими и описательными.
Функции == Комментарии
Функции должны быть короткими и делать только что-то одно. Если это что-то большое, имеет смысл разбить функцию на несколько меньших. Иногда следовать этому правилу непросто, но это определённо хорошее правило.
Небольшие функции не только облегчают тестирование и отладку – само существование таких функций выполняет роль хороших комментариев!
Например, сравним ниже две функции showPrimes(n) . Каждая из них выводит простое число до n .
Первый вариант использует метку nextPrime :
function showPrimes(n) < nextPrime: for (let i = 2; i < n; i++) < for (let j = 2; j < i; j++) < if (i % j == 0) continue nextPrime; >alert( i ); // простое > >
Второй вариант использует дополнительную функцию isPrime(n) для проверки на простое:
function showPrimes(n) < for (let i = 2; i < n; i++) < if (!isPrime(i)) continue; alert(i); // простое >> function isPrime(n) < for (let i = 2; i < n; i++) < if ( n % i == 0) return false; >return true; >
Второй вариант легче для понимания, не правда ли? Вместо куска кода мы видим название действия ( isPrime ). Иногда разработчики называют такой код самодокументируемым.
Таким образом, допустимо создавать функции, даже если мы не планируем повторно использовать их. Такие функции структурируют код и делают его более понятным.
Итого
Объявление функции имеет вид:
function имя(параметры, через, запятую) < /* тело, код функции */ >
- Передаваемые значения копируются в параметры функции и становятся локальными переменными.
- Функции имеют доступ к внешним переменным. Но это работает только изнутри наружу. Код вне функции не имеет доступа к её локальным переменным.
- Функция может возвращать значение. Если этого не происходит, тогда результат равен undefined .
Для того, чтобы сделать код более чистым и понятным, рекомендуется использовать локальные переменные и параметры функций, не пользоваться внешними переменными.
Функция, которая получает параметры, работает с ними и затем возвращает результат, гораздо понятнее функции, вызываемой без параметров, но изменяющей внешние переменные, что чревато побочными эффектами.
- Имя функции должно понятно и чётко отражать, что она делает. Увидев её вызов в коде, вы должны тут же понимать, что она делает, и что возвращает.
- Функция – это действие, поэтому её имя обычно является глаголом.
- Есть много общепринятых префиксов, таких как: create… , show… , get… , check… и т.д. Пользуйтесь ими как подсказками, поясняющими, что делает функция.
Функции являются основными строительными блоками скриптов. Мы рассмотрели лишь основы функций в JavaScript, но уже сейчас можем создавать и использовать их. Это только начало пути. Мы будем неоднократно возвращаться к функциям и изучать их всё более и более глубоко.
Задачи
Обязателен ли "else"?
важность: 4
Следующая функция возвращает true , если параметр age больше 18 .
В ином случае она запрашивает подтверждение через confirm и возвращает его результат:
function checkAge(age) < if (age >18) < return true; >else < // . return confirm('Родители разрешили?'); >>
Будет ли эта функция работать как-то иначе, если убрать else ?
function checkAge(age) < if (age >18) < return true; >// . return confirm('Родители разрешили?'); >
Есть ли хоть одно отличие в поведении этого варианта?
Оба варианта функций работают одинаково, отличий нет.
Перепишите функцию, используя оператор '?' или '||'
важность: 4
Следующая функция возвращает true , если параметр age больше 18 .
В ином случае она задаёт вопрос confirm и возвращает его результат.
function checkAge(age) < if (age >18) < return true; >else < return confirm('Родители разрешили?'); >>
Перепишите функцию, чтобы она делала то же самое, но без if , в одну строку.
Сделайте два варианта функции checkAge :
- Используя оператор ?
- Используя оператор ||
function checkAge(age) < return (age >18) ? true : confirm('Родители разрешили?'); >
Используя оператор || (самый короткий вариант):
function checkAge(age) < return (age >18) || confirm('Родители разрешили?'); >
Обратите внимание, что круглые скобки вокруг age > 18 не обязательны. Они здесь для лучшей читаемости кода.
Функции
В общем случае, функция — это "подпрограмма", которую можно вызывать из внешнего (или внутреннего, в случае рекурсии) по отношению к функции кода. Как и сама программа, функция состоит из последовательности инструкций, называемой телом функции. Значения могут быть переданы в функцию, а функция вернёт значение.
В JavaScript функции являются объектами первого класса, то есть: они являются объектами и с ними можно взаимодействовать и передавать их точно так же как любой другой объект. Если быть точным, функции — это объекты Function .
Больше подробностей и примеров можно найти в руководстве по функциям в JavaScript.
Описание
Каждая функция в JavaScript — это объект Function. О свойствах и методах объектов Function можно прочитать в статье Function .
Функции — это не процедуры. Функция всегда возвращает значение, а процедура может возвращать, а может не возвращать.
Чтобы вернуть значение, отличное от значения по умолчанию, в функции должна быть инструкция return , которая указывает, что именно нужно вернуть. Функция без него вернёт значение по умолчанию. В случае конструктора, вызванного с ключевым словом new , значение по умолчанию — это значение его параметра this . Для остальных функций значением по умолчанию будет undefined .
Параметры вызова функции называются аргументами функции. Аргументы передаются в функцию по значению. Если функция изменяет значение аргумента, это изменение не отражается на глобальном состоянии или вызывающей функции. Однако ссылки на объекты — это тоже значения, и они отличаются тем, что если функция изменяет свойства объекта по ссылке, это изменение видно снаружи функции, как показано в примере ниже.
/* Объявляем функцию 'myFunc' */ function myFunc(theObject) theObject.brand = "Toyota"; > /* * Объявляем переменную 'mycar'; * создаём и инициализируем новый Object; * приравниваем 'mycar' к ссылке на него */ var mycar = brand: "Honda", model: "Accord", year: 1998, >; /* Выведет 'Honda' */ console.log(mycar.brand); /* Передаём ссылку на объект в функцию */ myFunc(mycar); /* * Выведет 'Toyota', так как значение свойства 'brand' * было изменено внутри функции. */ console.log(mycar.brand);
Ключевое слово this не ссылается на функцию, которая выполняется в данный момент, поэтому вы должны обращаться к объектами Function по имени, даже внутри тела самой функции.
Определение функций
Есть несколько способов определить функцию:
Объявление функции (инструкция function )
Специальный синтаксис для объявления функций (более подробно: function statement):
function name([param[, param[, . param]]])
Имя аргумента, передаваемого в функцию. У функции может быть не более 255 аргументов.
Инструкции, из которых состоит тело функции.
Функция-выражение (оператор function )
Функция-выражение похожа на определение функции и имеет такой же синтаксис (более подробно: function operator):
function [name]([param] [, param] [. param])
Имя функции. Может быть не указано, в таком случае функция становится анонимной.
Имя аргумента, передаваемого в функцию. У функции может быть не более 255 аргументов.
Инструкции, из которых состоит тело функции.
Стрелочная функция-выражение (=>)
Примечание: стрелочные функции являются экспериментальной технологией*,* частью спецификации ECMAScript 6 и пока что не поддерживаются всеми браузерами.
Стрелочные функции отличаются более кратким синтаксисом и тем, что они лексически связывают значение своего this (подробнее об этом в статье Стрелочные функции):
([param] [, param]) => < statements >param => expression
Имя параметра. Если параметров нет, вместо них нужно поставить (). Если параметров больше одного, их также нужно заключить в ().
Если инструкций несколько, их нужно заключить в <>. Для одного выражения фигурных скобок не требуется, а результат этого выражения будет возвращён функцией (то есть` функция x => 3 + 8 вернёт 11).```
Конструктор Function
Примечание: Использовать конструктор Function не рекомендуется, так как он принимает тело функции в виде строки, а это может помешать оптимизациям, которые выполняют движки JavaScript, а также привести к другим проблемам.
Объекты Function можно создавать с помощью оператора new (как и любые другие объекты):
new Function (arg1, arg2, . argN, functionBody)
Ноль или больше имён параметров функции. Имя должно быть строкой, содержащей валидный идентификатор JavaScript. Если параметров несколько, они должны быть разделены запятыми. Например: " x ", " theValue ", или " a,b ".
Инструкции, из которых состоит тело функции.
Конструктор Function можно вызывать и без оператора new, эффект будет тем же.
Параметры функции
Примечание: Остаточные параметры и параметры по умолчанию — это экспериментальная технология, часть спецификации ECMAScript 6, и они пока ещё не получили широкой поддержки среди браузеров.
Параметры по умолчанию
Параметры функции по умолчанию позволяют инициализировать формальные параметры со значениями по умолчанию, если им не было передано значение, или было передано undefined . Подробнее о них можно узнать в статье Параметры по умолчанию.
Остаточные параметры
Синтаксис оставшихся параметров позволяет передать бесконечное число аргументов как массив. Подробности можно найти в статье Остаточные параметры.
Объект arguments
Внутри функции получить доступ к её аргументам можно через объект arguments.
- arguments (en-US): Объект, похожий на массив и содержащий все аргументы, переданные в текущую функцию.
- arguments.callee (en-US) Устарело : Функция, исполняемая в текущий момент.
- arguments.caller : Функция, которая вызвала текущую функцию.
- arguments.length (en-US): Число аргументов, переданных в функцию.
Определение методов
Геттеры и сеттеры
Можно определять геттеры (методы для чтения) и сеттеры (методы для изменения) для любого встроенного или пользовательского объекта, который поддерживает добавление новых свойств. Для этого используется синтаксис литерала объекта.
Связывает свойство объекта с функцией, которая будет вызвана при обращении к свойству.
Связывает свойство объекта с функцией, которая будет вызвана при попытке изменения свойства.
Синтаксис определения методов
Примечание: Определение методов — это экспериментальная технология, часть спецификации ECMAScript 6, и она пока ещё не получила широкой поддержки среди браузеров.
Начиная с ECMAScript 6, можно определять собственные методы, используют более краткий синтаксис, похожий на геттеры и сеттеры. Более подробно — в статье Определение методов.
var obj = foo() >, bar() >, >;
Сравнение конструкторов Function с объявлением функций и функциями-выражениями
Посмотрите на следующие примеры:
Функция, определённая через конструктор Function и приравненная к переменной multiply:
var multiply = new Function("x", "y", "return x * y");
Объявление функции multiply:
function multiply(x, y) return x * y; >
Анонимная функция-выражение, приравненная к переменной multiply:
var multiply = function (x, y) return x * y; >;
Функция-выражение с именем func_name , приравненное к переменной multiply:
var multiply = function func_name(x, y) return x * y; >;
Отличия
Во всех случаях результат примерно одинаков, но есть несколько нюансов:
Имя функции и переменная, к которой функция приравнена — это не одно и то же. Имя функции нельзя менять, а вот переменной, к которой приравнена функция, можно дать другое значение. В случае функции-выражения с именем, это имя может быть использовано только внутри самой функции. При попытке использовать его снаружи возникнет ошибка (а если ранее была объявлена переменная с таким именем, будет возвращено undefined ). Например:
var y = function x() >; alert(x); // выкинет ошибку
Также имя функции-выражения проявляется, если сериализовать функцию через метод Function.toString.
А вот переменная, к которой функция приравнена, ограничена только собственной областью видимости, которая включает ту область, где функция была объявлена.
Как показано в четвёртом примере, имя функции может отличаться от имени переменной, к которой функция приравнена, эти имена никак не связаны. Объявление функции (function declaration) также создаёт и переменную с именем, аналогичным имени функции. Таким образом:
- Если функция определена с помощью функции-выражения (function expression), её имя доступно только внутри самой функции.
- Если функция объявлена (function declaration), её имя доступно в той области видимости, где функция была определена.
У функции, определённой с помощью ' new Function' , нет имени. Однако, в JavaScript движке SpiderMonkey, сериализованное представление функции отображается так, как будто оно имеет имя "anonymous". Например, , alert(new Function()) выдаст:
function anonymous() >
Так как на самом деле у функции нет имени, переменную anonymous нельзя использовать внутри функции. Например, следующий пример выкинет ошибку:
var foo = new Function("alert(anonymous);"); foo();
В отличии от функций, определённых через функцию-выражение или конструктор Function , функция, определённая через объявление, может быть использована перед тем, как была определена. Например:
foo(); // выведет FOO! function foo() alert("FOO!"); >
Функция, определённая через функцию-выражение, наследует текущую область видимости, то есть создаёт замыкание. А вот функция, созданная с помощью конструктора Function , не наследует ничего, кроме глобальной области видимости (её наследуют вообще все функции).
Функции, определённые через функцию-выражение и объявление функции парсятся только один раз, в отличии от функций, созданных с помощью конструктора. То есть строка, которая передаётся в конструктор Function , парсится при каждом вызове конструктора. И хотя функция-выражение каждый раз создаёт замыкание, тело функции при этом не парсится, и получается, что функции-выражение всё равно быстрее, чем " new Function(. ) ". Поэтому конструктора Function в большинстве случаев стоит избегать, если это возможно.
Стоит отметить, что функции-выражения и объявления функций внутри функции, созданной при парсинге конструктора Function , парсятся только один раз. Например:
var foo = new Function( "var bar = 'FOO!';\nreturn(function() );", )(); foo(); // "function() " Эта часть строки, составляющей тело функции, не парсится во второй раз.
Объявление функции можно очень легко (и часто случайно) превратить в функцию-выражение. Объявление функции перестаёт быть таковым, если оно:
- становится частью выражения
- не является "исходным элементом" функции или файла. Исходный элемент - это не вложенный элемент внутри функции или скрипта:
var x = 0; // исходный элемент if (x == 0) // исходный элемент x = 10; // не исходный элемент function boo() > // не исходный элемент > function foo() // исходный элемент var y = 20; // исходный элемент function bar() > // исходный элемент while (y == 10) // исходный элемент function blah() > // не исходный элемент y++; // не исходный элемент > >
Примеры
// объявление функции function foo() > // функция-выражение (function bar() >); // функция-выражение x = function hello() >; if (x) // функция-выражение function world() > > // объявление функции function a() // объявление функции function b() > if (0) // функция-выражение function c() > > >
Определение функции в зависимости от условия
Функции могут быть определены в зависимости от условий с помощью инструкции function (разрешённое расширение стандарта ECMA-262 Edition 3) или конструктора Function . Обратите внимание, что подобные инструкции запрещены в ES5 strict. Кроме того, эта возможность по-разному ведёт себя в разных браузерах, поэтому не стоит на неё рассчитывать.
В коде ниже функция zero никогда не будет определена и не может быть вызвана, потому что ' if (0) ' всегда расценивается как false :
if (0) function zero() document.writeln("This is zero."); > >
Если изменить условие на ' if (1) ', функция zero будет определена.
Заметьте, что хотя это выглядит как объявление функции, на самом деле, это функция-выражение (или инструкция), так как она вложена внутрь другой инструкции. Изучите разницу между объявлением функции и функцией-выражением.
Некоторые JavaScript-движки (но не SpiderMonkey), неверно считают любую функцию-выражение с именем за объявление функции. Это приводит к тому, что функция zero будет определена, даже если условие всегда false . Более безопасный способ определить функцию по условию - это сделать её анонимной и приравнять к переменной:
if (0) var zero = function () document.writeln("This is zero."); >; >
Примеры
Пример: возврат отформатированного числа
Эта функция возвращает строку, содержащую число с заданным количеством нулей перед ним:
function padZeros(num, totalLen) var numStr = num.toString(); // Инициализировать возвращаемое значение в виде строки var numZeros = totalLen - numStr.length; // Посчитать число нулей в начале for (var i = 1; i numZeros; i++) numStr = "0" + numStr; > return numStr; >
var result; result = padZeros(42, 4); // возвращает "0042" result = padZeros(42, 2); // возвращает "42" result = padZeros(5, 4); // возвращает "0005"
Пример: существует ли функция
Можно определить, существует ли функция с помощью оператора typeof . В следующем примере проверяется, есть ли у объекта window функция noFunc . Если есть, то она вызывается; если нет, выполняется какое-то другое действие.
if ("function" == typeof window.noFunc) // вызывать noFunc() > else // сделать что-то другое >
Заметьте, что в проверке условия используется ссылка на noFunc — после имени функции нет скобок, поэтому сама функция не вызывается.
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-function-definitions |
Поддержка браузерами
BCD tables only load in the browser
Смотрите также
- Function
- Инструкция function
- Оператор function
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 4 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2023 by individual mozilla.org contributors. Content available under a Creative Commons license.
Основы JavaScript: функции
Функции могут считаться как одними из ключевых строительных блоков JavaScript программ. Функция — это просто набор команд, разработанных для выполнения определенной задачи, которая исполняется при вызове.
Определение функции
Определение функции (также известное как объявление, или инструкция функции) всегда начинается с ключевого слова function . Затем следует название этой функции, параметры, которые помещаются в скобки. Дальше идут инструкции JavaScript, которые располагаются в фигурных скобках < >и которые исполняются при вызове функции.
Посмотрим на примере:
function multiply(a, b) return a * b;
>
Наша функция multiply принимает два аргумента ( a и b ). Внутри фигурных скобок располагается утверждение, которое возвращает результат умножения первого параметра a на второй b .
Функциональное выражение
Есть еще один способ определения функции, а именно, функциональное выражение. Такие типы функций могут быть анонимными. Нет необходимости давать им название.
Например, предыдущую функцию multiply можно записать следующим образом:
let multiply = function(a, b) < return a * b; >;
let x = multiply(2,2); // x возвращает 4
Также распространённым случаем является передача функционального выражения в качестве аргумента другой функции.
Еще мы можем определить функцию, основанную на условии. Например, следующая функция addItem сработает, только если num будет равно 1:
let addItem;
if (num === 1) addItem = function(shoppingList) shoppingList.item = 'Apples';
>
>
Вызов функции
При определении функции все, что нам необходимо сделать, это дать ей название и передать инструкции, которые исполнятся при ее вызове.
Для того, чтобы вызвать нашу функцию multiply необходимо записать:
multiply(2,2);
В данном случае мы вызываем функцию, которая принимает два аргумента со значениями 2 и 2 . Функция исполняется и проходит все инструкции, возвращая значение 4 (2 умноженное на 2).
Функция должна быть в области видимости при вызове, однако определение функции может быть поднято (располагаться ниже вызова в коде), например:
console.log(multiply(2,2));
/* . */
function multiply(a,b)
Область видимости функции — это либо та функция, в которой она определена, либо вся программа, если определение функции находится на глобальном уровне.
Обратите внимание: это работает только со стандартной функцией, а не с функциональным выражением.
Область видимости функции
Когда переменные определяются внутри функции, к ним нельзя получить доступ извне, так как это будет вне ее области видимости. Однако у функции есть доступ ко всем переменным и функциям внутри области видимости, в которой она определена. Так, функция, определенная в глобальной области видимости, имеет доступ ко всем переменным в этой области. Функция, определенная внутри другой функции, имеет доступ только к переменным, которые находятся в родительской функции, а также к тем, что для нее доступны.
Посмотрим на примере:
// Эти переменные находятся в глобальной области видимостиlet a = 10,
b = 3,
name = 'Bruce';// Эта функция определена глобальноfunction multiply() return a * b;
>multiply(); // returns 30// Работаем с вложенной функциейfunction multiplyAssign() let a = 20,
b = 6;
function add() return name + ‘ received’ + (a * b);
>
return add();
>multiplyAssign(); //возвращает "Bruce received 120"
Вложенные функции и замыкания
Итак, функцию можно вложить в другую функцию! Вложенная (внутренняя) функция является «собственностью» внешней функции. Она также образует замыкание. Замыкание — это выражение (обычно функция), которое может содержать свободные переменные в окружении, связывающем эти переменные («закрывает» выражение).
function addSquares(a, b) function square(x) return x * x;
>
return square(a) + square(b);
>
a = addSquares(2, 3); // возвращает 13
b = addSquares(3, 4); // возвращает 25
c = addSquares(4, 5); // возвращает 41
Так как вложенная функция образует замыкание, можно вызвать внешнюю функцию и задать аргументы как для внешней, так и для внутренней функции.
Замыкания
Как нам уже известно, мы можем «вкладывать» функции, и JavaScript даст вложенной функции полный доступ ко всем переменным и функциям, определенным внутри внешней функции (а также к переменным и функциям, которые для нее доступны).
Однако внешняя функция не имеет доступа к переменным и функциям, определенным внутри вложенной функции! Замыкание создается, когда внутренняя функция каким-то образом становится доступной для области видимости внешней функции.
Посмотрим на примере:
// внешняя функция определяет переменную namelet user = function(name) <
let getName = function() return name;// у внутренней функции есть доступ к переменной "name"
>// возвращаем внутреннюю функцию, и используем во внешней области видимостиreturn getName;
>
makeAdmin = user('Steve Stevesson');
makeAdmin(); // возвращает "Steve Stevesson"
Объект arguments
Аргументы любой функции содержатся в объекте, похожем на массив. Внутри функции вы можете обратиться к передаваемым аргументам следующим образом:
arguments[i]
В данном случае i — это первый индекс аргумента, начинающийся с нуля. Поэтому первый аргумент, передаваемый в функцию будет arguments[0] . Общее число аргументов можно получить через arguments.length .
Используя объект arguments , вы можете вызвать функцию с большим количеством аргументов, чем было заявлено при ее определении. Это часто бывает удобно, если вы заранее не знаете, сколько аргументов будет передано функции. Вы можете использовать arguments.length для определения количества аргументов, которое было заявлено изначально, а затем обратиться к каждому из них, используя объект arguments .
Например, рассмотрим функцию, которая соединяет несколько строк. Единственный формальный аргумент для функции — это строка, которая определяет, какими знаками разделять элементы. Функция будет выглядеть следующим образом:
function myConcat(separator) let result = ''; // инициализация списка
let i;
// пройдемся циклом по аргументам
for (i = 1; i < arguments.length; i++) result += arguments[i] + separator;
>
return result;
>
Вы можете передать любое число аргументов в данную функцию, и она соединит каждую строку в новую строку ‘list’:
myConcat(', ', 'fred', 'wilma', 'barney', 'betty');
// возвращает "fred, wilma, barney, betty, "myConcat('; ', 'marge', 'homer', 'lisa', 'bart', 'maggie');
// возвращает "marge; homer; lisa; bart; maggie; "myConcat('. ', 'jerry', 'kramer', 'elaine', 'george', 'newman');
// возвращает "jerry. kramer. elaine. george. newman. "
Обратите внимание: переменная arguments только похожа на массив, но это не так. У нее есть пронумерованный индекс и длина. Однако у нее нет всех методов, присущих массиву.
Параметры функции
Существует два вида параметров функции: параметры, используемые по умолчанию и оставшиеся параметры. Давайте остановимся на них подробнее.
Параметры по умолчанию
В JavaScript параметры функции по умолчанию равняются undefined . Однако в некоторых ситуациях полезно установить значение по умолчанию. Здесь и пригождаются параметры по умолчанию.
Это очень просто воплотить:
function multiply(a, b = 1) return a * b;
>multiply(10); // 10
Вы можете установить 1 как значение по умолчанию для переменной b в начале функции.
Оставшиеся параметры
Синтаксис оставшихся параметров позволяет передавать неопределенное количество параметров в функцию.
В данном примере мы используем оставшиеся параметры, чтобы собрать аргументы, начиная со второго и до конца. Затем мы перемножаем их на первый. Пример использует стрелочную функцию, которую мы разберем дальше:
function multiply(multiplier, . theArgs) return theArgs.map(x => multiplier * x);
>let arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
Стрелочные функции
Синтаксис стрелочной функции гораздо короче, по сравнению с обычным выражением функции. Например, обычная функция выглядит так:
function funcName(params) return params + 10;
>funcName(2); // 12
А вот та же функция, но записанная как стрелочная:
let funcName = (params) => params + 10funcName(2); // 12
Та функция, но записанная всего в одну строчку! Очень компактно!
Если у нас нет параметров, стрелочная функция записывается следующим образом:
Если же у нас всего один параметр, скобки не обязательны:
parameters =>
И наконец, если вы возвращаете выражение, скобки можно убрать:
parameters => expression// то же самое, что и:function (parameters) return expression;
>
Обратите внимание: в отличие от обычных функций, стрелочная функция не имеет привязки к this . Вместо этого, this оставляет значение из своего первоначального контекста.
Предопределенные функции
Стоит отметить, что в JavaScript есть множество встроенных функций! И вероятнее всего, они сэкономят вам много времени, особенно для решения простых задач.
Заключение
На сегодня все! Мы узнали об определении функций, функциональных выражениях, вызове функций, области видимости, вложенности, замыканиях, объекте arguments, параметрах функции и стрелочных функциях.
- 3 вида циклов for в JavaScript
- JavaScript Symbols. Новый тип примитивов
- Краткое введение в функции высшего порядка в JavaScript
