Функции Javascript
Функция JavaScript это блок кода, предназначенный для выполнения определенной задачи. По сути функции JavaScript это то же самое, что и процедуры или подпрограммы в других языках программирования.
Функция JavaScript выполняется только тогда, когда «что-то» обращается к ней (вызывает ее).
function myFunction(p1, p2) < return p1 * p2; // Эта функция возвращает производное p1 и p2 >
Синтаксис функции JavaScript
Функция JavaScript декларируется при помощи ключевого слова function, за которым следует имя и круглые скобки ().
Имя функции может состоять из букв, цифр, символа подчеркивания и символа доллара (те же правила, что и с переменными).
Внутри круглых скобок могут определяться имена параметров, разделенных запятыми: (параметр1, параметр2, . ).
Исполняемый код функции помещается внутри фигурных скобок <>:
function имя(параметр1, параметр2, параметр3)
Параметры функции перечисляются внутри круглых скобок () в заголовке функции.
Аргументы функции это значения, получаемые функцией при ее вызове.
Внутри функции аргументы (параметры) ведут себя как локальные переменные.
Вызов функции
Код внутри функции выполняется только тогда, когда «что-то» обращается к ней (вызывает ее):
- Когда возникает некое событие (например, пользователь нажимает на кнопку)
- Когда функция вызывается из JavaScript кода
- Автоматически (самовызов)
Подробнее обо всем этом будет рассказано дальше в этом учебнике.
Возвращение результата из функции
Когда интерпретатор JavaScript достигает выражения return, функция прекращает выполняться.
Если функция была вызвана из кода, интерпретатор вернет выполнение коду, расположенному после вызвавшего функцию выражения.
Часто функции возвращают некое значение, которое они вычисляли. Возвращенное значение «возвращается» обратно тому, кто ее вызвал.
Вычислим произведение двух чисел и вернем результат:
// Вызывается функция, возвращаемое значение // сохраняется в переменной x var x = myFunction(4, 3); function myFunction(a, b) < return a * b; // Функция возвращает произведение от a и b >
В результате в переменной x будет получено значение 12.
Зачем нужны функции?
Функции позволяют повторно использовать код: однажды определите некий блок кода и затем используйте его сколько угодно раз из любого места скрипта.
Вы можете использовать один и тот же код много раз с разными аргументами, чтобы получить разные результаты.
В следующем примере используется функция, конвертирующая градусы Фаренгейта в градусы Цельсия:
function toCelsius(fahrenheit) < return (5/9) * (fahrenheit-32); >document.getElementById("demo").innerHTML = toCelsius(77);
Оператор () вызывает функцию
Если брать пример кода, приведенный ранее, то выражение toCelsius обращается к объекту функции, а выражение toCelsius() к результату функции.
Вызов функции без скобок () вернет определение функции, а не результат ее работы:
function toCelsius(fahrenheit) < return (5/9) * (fahrenheit-32); >document.getElementById("demo").innerHTML = toCelsius;
Функции как значения переменных
Функции могут использоваться так же как переменные во всех типах формул, в операторах присваивания и в любых вычислениях.
Вместо использования переменной для хранения результата функции:
var x = toCelsius(77); var text = "Температура - " + x + " градусов Цельсия";
Вы можете использовать саму функцию, как значение переменной:
var text = "Температура - " + toCelsius(77) + " градусов Цельсия";
Подробнее о функциях будет рассказано в следующих главах этого учебника.
#10 – Функции в языке JavaScript
Функции в JavaScript выполняют важную роль. Они обеспечивают сокращение кода за счет вынесения его в отдельные места. За урок мы научимся создавать функции и передавать в них значения.
Видеоурок
Функции можно назвать небольшими подпрограммами, куда можно вынести повторяющийся код и обращаться к нему, когда это будет нужно. Функции значительно облегчают построение программ, так как нам не надо копировать однотипный код множество раз, а можно просто воспользоваться одной общей функцией.
Многие путают функции и методы и не понимают отличий между ними. На самом деле отличий нет, так как что методы, что функции являются одним и тем же. Функции что записаны вне классов называют функциями, а функции что записаны внутри классов называются методами.
Точно такая же ситуация обстоит с переменным. В классах переменные называются полями, а вне классов — переменными.
В JS функции создаются при помощи ключевого слова function . Каждая функция может иметь какие-либо параметры или же не иметь их вовсе. Функции способны что-либо возвращать в ходе выполнения кода, если это требуется.
Создание функции
На основе всех данных наша функция будет выглядеть следующим образом:
function test()
Функция выше не принимает никаких параметров и ничего не возвращает. Она просто пишет слово в консоль. Давайте разнообразим функцию и добавим параметр:
function test(word)
Теперь функция принимает параметр, который будет отображен в консоли.
Если функция должна что-либо вернуть, то прописываем тип данных который будет возвращен. Для возвращения данных используем ключевое слово return :
function test(some_number)
Локальные и глобальные переменные
В JavaScript есть несколько полей видимости: локальная и глобальная. Если записать глобальную переменную, то такая переменная будем видна повсюду и с ней можно работать отовсюду в документе. Если записать локальную переменную, то такая переменная будет видна лишь в той области, где она записана.
Для создания глобальной переменной её необходимо прописать вне функции, класса или же объекта. Для создания локальных переменных вам достаточно поместить переменную в функцию. Переменная в функции будет видна лишь внутри блока с функцией и нигде более.
Весь код будет доступен после подписки на проект!
Объект функции, 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; >
Функциональное программирование
Функции представляют собой набор инструкций, которые можно повторно вызывать в различных частях программы по имени функции. В общем случае синтаксис определения функции выглядит следующим образом:
function имя_функции(параметры) < // Инструкции >
Определение функции начинается с ключевого слова function , после которого следует имя функции. Наименование функции подчиняется тем же правилам, что и наименование переменной: оно может содержать только цифры, буквы, символы подчеркивания и доллара ($) и должно начинаться с буквы, символа подчеркивания или доллара.
После имени функции в скобках идет перечисление параметров. Даже если параметров у функции нет, то просто идут пустые скобки. Затем в фигурных скобках идет тело функции, содержащее набор инструкций.
Определим простейшую функцию:
function hello()
Данная функция называется hello() . Она не принимает никаких параметров и все, что она делает, это выводит на консоль браузера строку «Hello Metanit.com».
Чтобы функция выполнила свою работу, нам надо ее вызвать. Общий синтаксис вызова функции:
имя_функции(параметры)
При вызове после имени вызываемой функции в скобках указывается список параметров. Если функция не имеет параметров, то указывются пустые скобки.
Например, определим и вызовем простейшую функцию:
METANIT.COM
В данном случае функция hello не принимает параметров, поэтому при ее вызове указываются пустые скобки:
hello();
Отличительной чертой функций является то, что их можно многократно вызывать в различных местах программы:
// определение функции function hello() < console.log("Hello Metanit.com"); >// вызов функции hello(); hello(); hello();
Переменные и константы в качестве функций
Подобно тому, как константам и переменным присваиваются простейшие значения (числа, строки и т.д.), также им можно присваивать функции. Затем через такую переменную или константу можно вызвать присвоенную ей функцию:
METANIT.COM
Присвоив константе или переменной функцию:
const message = hello;
затем мы можем по имени константы/переменной вызывать эту функцию:
message();
Также мы можем динамически менять функции, которые хранятся в переменной:
function goodMorning() < console.log("Доброе утро"); >function goodEvening() < console.log("Добрый вечер"); >let message = goodMorning; // присваиваем переменной message функцию goodMorning message(); // Доброе утро message = goodEvening; // меняем функцию в переменной message message(); // Добрый вечер
Функции-выражения и анонимные функции
Необязательно давать функциям определенное имя. Можно использовать анонимные функции. Такие функции при определении присваиваются константе или переменной. Эти функции еще называют функции-выражения (function expression):
const message = function() < console.log("Hello JavaScript"); >message();
Используя имя константы или переменной, которой присвоена функция, можно вызывать эту функцию.
Локальные функции
JavaScript позволяет определять локальные функции — функции внутри других функций. Локальные функции видно только в рамках внешней функции, в которой они определены. Например:
function print() < printHello(); printHello(); printHello(); function printHello()< console.log("Hello"); >> print(); printHello(); // Uncaught ReferenceError: printHello is not defined - локальную функцию можно вызвать только из ее окружающей функции
Здесь внутри функции print определена локальная функция printHello, которая просто выводит строку «Hello». И внутри функции print мы можем вызвать локальную функцию printHello, однако вне окружающей функции локальную функцию вызвать нельзя.
Данный пример довольно простой и не имеет большого смысла. Однако, как правило, локальные функции определяются для таких действий, которые применяются многократно только в рамках какой-то одной функции и больше нигде. К минусам локальных функции можно отнести то, что они создаются всякий раз, когда происходит вызов внешней функции.