Функции в JavaScript
Если вы совсем не знаете, что такое подпрограммы в программировании, и, в частности, функции, то переходите по указанным ссылкам и изучайте. Здесь говорим только об особенностях использования функций в JavaScript.
Как всегда напоминаю, что полный обучающий курс по JavaScript можно найти здесь:
Поскольку JavaScript основан на С++, то в нём также нет такого понятия, как “процедура”, а все подпрограммы исполняются исключительно как функции.
Функции могут быть встроенными (то есть “входить в комплект” языка программирования, а могут быть пользовательскими (то есть функциями, которые создаёт сам программист).
Определение функций в JavaScript
Функция в JavaScript определяется с помощью ключевого слова function , после которого следуют имя функции (допустимый в JavaScript идентификатор), список параметров функции (аргументов), и тело функции (исходный код функции). Синтаксис:
Функция может не иметь параметров. В этом случае она определяется так:
То есть круглые скобки обязательны, независимо от того, есть у функции параметры или нет.
Параметры заключаются в круглые скобки и перечисляются через запятую. Параметры являются локальными переменными функции, в которую они передаются. Пример:
Здесь мы объявляем и определяем функцию с именем MsgToScreen, в которую можно передавать два параметра. Как вы помните, JavaScript — это не типизированный язык, поэтому параметры могут быть любых типов.
Тело функции заключено в фигурные скобки. Обратите внимание на то, что после инструкции document.write(Msg + ‘: ‘ + Num) нет точки с запятой. Это допустимо для последней инструкции в блоке (в нашем случае — в функции). Однако это плохая привычка — лучше ставить точку с запятой после всех инструкций.
В этом примере функция не возвращает результат. То есть в терминологии Паскаля это была бы процедура. Однако в С/С++, JavaScript и других подобных языках — это просто функция, которая не возвращает результат.
Ниже пример функции, которая возвращает результат:
Для возвращения результата функцией используется ключевое слово return. После этого слова находится результат — какое-либо допустимое выражение JavaScript. Это выражение можно заключить в круглые скобки, но это не обязательно.
ПРИМЕЧАНИЕ
На самом деле любая функция возвращает результат. Просто если вы НЕ используете слово return , то функция вернёт значение undefined .
В этом примере в функцию передаются два значение, в теле функции эти значения складываются и результат сложения возвращается сценарию или функции, из которых вызвана функция CalcSum . Кстати, пришло время поговорить о вызове функций.
Вызов функций в JavaScript
Функция объявляется и определяется один раз, а может быть вызвана в сценарии (или из других функций) сколько угодно раз. Собственно, в этом и смысл создания функций — сокращение размера исходного кода.
Чтобы вызвать функцию, надо просто написать её имя, а в круглых скобках перечислить параметры. Пример вызова функций, которые мы определили выше:
Как видите, функция, которая возвращает результат, может передаваться как параметр в другую функцию (в нашем случае мы результат работы функции CalcSum() передаём во встроенную функцию alert()). Последний пример отобразит на экране окно с числом 150:

ВАЖНО!
JavaScript не проверяет соответствие типов параметров. Поэтому будьте внимательны. Количество параметров также не проверяется. Если вы передадите параметров больше, чем надо, то лишние аргументы просто не будут использованы. Если передадите меньше, чем надо, то отсутствующим аргументам будет присвоено значение undefined .
Вложенные функции в JavaScript
При необходимости вы можете внутри функции определить другие функции. Такие функции называются вложенными. Пример:
В этом примере функции CalcSum и CalcMul являются вложенными в функцию Calc . Эти функции видны только в функции Calc и не могут быть использованы в другом месте сценария или в других внешних функциях. Также функции, определённые с помощью ключевого слова function , могут быть вложены только в другие функции, и не могут быть вложены в другие блоки кода (например, в циклы).
Кстати, если вы изучали предыдущие мои статьи, то вы догадались, что будет выведено на экран в этом примере. Если нет, что почитайте вот эту статью.
Функциональные литералы в JavaScript
В JavaScript можно определять функциональные литералы. В отличие от обычного объявления функции, которое мы рассмотрели выше, при создании функционального литерала не требуется указывать имя функции. Пример:
Здесь сначала мы создали обычную функцию. А затем — функциональную переменную (функциональный литерал).
То есть во втором случае у функции нет имени, но есть переменная, связанная с этой функцией.
Здесь мы создали массив функций, а потом перебрали этот массив в цикле и поочерёдно вызвали все эти функции.
ВАЖНО!
Не забывайте, что язык JavaScript чувствителен к регистру, поэтому при именовании функций и последующем их вызове имейте это ввиду — функции (переменные) mf и MF — это разные функции.
Зачем вообще нужен такой способ?
Ну, например, когда функция используется только один раз и нет необходимости придумывать ей имя. Или как в показанном выше примере есть несколько похожих функций, которые удобно сохранить в массив и использовать ту или иную функцию в зависимости от значения какого-либо параметра.
Кроме того, при использовании функциональных литералов может быть создана функция, возвращающая функцию:
Здесь переменная у — это функция. Поэтому этот пример выведет на экран число 500, то есть результат выполнения функции z , которая была возвращена в результате выполнения функции test() . А если бы мы забыли про круглые скобки, и сделали так:
document.write(y);
То на экран уже был бы выведен НЕ результат работы функции, а значение переменной y, то есть:
потому что без круглых скобок это не функция, а функциональный литерал (функциональная переменная).
Эта статья — лишь капля в море знаний о JavaScript. Если хотите испить эту чашу до дна, то изучить этот язык, а также jQuery и Ajax можно здесь:
4.13 – Литералы
В программировании константа – это фиксированное значение, которое нельзя изменять. В C++ есть два типа констант: литеральные константы и символьные константы. В этом уроке мы рассмотрим литеральные константы, а в следующем – символьные константы.
Литеральные константы (обычно называемые просто литералами) – это значения, вставленные непосредственно в код. Например:
Они являются константами, потому что их значения не могут быть изменены динамически (сначала вы должны изменить их, а затем перекомпилировать программу, чтобы изменение вступило в силу).
Так же, как у объектов есть тип, тип есть и у всех литералов. Тип литерала предполагается из значения и формата самого литерала.
| Значение литерала | Примеры | Тип по умолчанию |
|---|---|---|
| целое число | 5, 0, -3 | int |
| логическое значение | true , false | bool |
| число с плавающей запятой | 3.4, -2.2 | double (не float ) |
| символ | ' a ' | char |
| строка в стиле C | " Hello, world! " | const char[14] |
Суффиксы литералов
Если тип литерала по умолчанию не соответствует необходимому, вы можете изменить тип литерала, добавив суффикс:
| Тип данных | Суффикс | Назначение |
|---|---|---|
| int | u или U | unsigned int |
| int | l или L | long |
| int | ul , uL , Ul , UL , lu , lU , Lu или LU | unsigned long |
| int | ll или LL | long long |
| int | ull , uLL , Ull , ULL , llu , llU , LLu или LLU | unsigned long long |
| double | f или F | float |
| double | l или L | long double |
Обычно вам не нужно использовать суффиксы для целочисленных типов, но вот пара примеров:
По умолчанию литеральные константы с плавающей точкой имеют тип double . Чтобы они были типа float , необходимо использовать суффикс f (или F ):
Начинающие программисты часто не понимают, почему следующий код работает не так, как ожидалось:
Поскольку у 4.1 нет суффикса, он рассматривается как литерал типа double , а не как литерал типа float . Когда C++ определяет тип литерала, ему всё равно, что вы делаете с литералом (например, в данном случае используете его для инициализации переменной float ). Следовательно, 4.1 необходимо преобразовать из double в float , прежде чем его можно будет присвоить переменной f , и это может привести к потере точности.
Литералы можно использовать в коде C++, если их значения понятны. Чаще всего это происходит при использовании для инициализации или присвоения значения переменной, выполнения математических операций или вывода текста на экран.
Строковые литералы
В уроке «4.11 – Символы» мы определили строку как набор последовательных символов. C++ поддерживает строковые литералы:
Строковые литералы в C++ по историческим причинам обрабатываются очень странно. На данный момент можно использовать строковые литералы для печати текста с помощью std::cout , но не пытайтесь назначать их переменным или передавать их функциям – это либо не сработает, либо будет работать не так, как вы ожидаете. О строках в стиле C (и о том, как обойти все эти странные проблемы) мы поговорим подробнее в будущих уроках.
Экспоненциальная запись для числовых литералов с плавающей запятой
Есть два разных способа объявить литералы с плавающей точкой:
Во второй форме число после экспоненты может быть отрицательным:
Литералы в восьмеричной и шестнадцатеричной системах счисления
В повседневной жизни мы считаем, используя числа в десятичной системе счисления, где каждая цифра может быть 0, 1, 2, 3, 4, 5, 6, 7, 8 или 9. Десятичная система счисления число также называется «с основанием 10», потому что в ней возможно использование 10 цифр (от 0 до 9). В этой системе мы считаем так: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,… По умолчанию, числа в программах на C++ считаются десятичными.
В двоичной системе счисления всего 2 цифры: 0 и 1, поэтому она называется «с основанием 2». В двоичном формате мы считаем так: 0, 1, 10, 11, 100, 101, 110, 111,…
Существуют две других системы счисления, которые иногда используются в вычислениях: восьмеричная и шестнадцатеричная.
Восьмеричная система счисления – с основанием 8, то есть доступны только цифры: 0, 1, 2, 3, 4, 5, 6 и 7. В восьмеричном формате мы считаем так: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12,… (примечание: цифр 8 и 9 нет, поэтому мы сразу переходим от 7 к 10).
| Десятичная система | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Восьмеричная система | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 |
Чтобы использовать литерал в восьмеричном формате, добавьте к вашему литералу префикс 0 (ноль):
Эта программа печатает:
Почему 10, а не 12? Потому что числа печатаются в десятичном формате, а 12 в восьмеричном формате = 10 десятичном формате.
Восьмеричная система счисления практически не используется, и мы рекомендуем вам ее избегать.
Шестнадцатеричная система счисления — с основанием 16. В шестнадцатеричной системе мы считаем так: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12,…
| Десятичная система | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Восьмеричная система | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | 10 | 11 |
Чтобы использовать литерал в шестнадцатеричном формате, добавьте к нему префикс 0x .
Эта программа печатает:
Поскольку для одной шестнадцатеричной цифры возможны 16 различных значений, мы можем сказать, что одна шестнадцатеричная цифра использует 4 бита. Следовательно, пара шестнадцатеричных цифр может использоваться для точного представления полного байта.
Рассмотрим 32-битное целое число со значением 0011 1010 0111 1111 1001 1000 0010 0110 . Из-за длины и повторяемости цифр его нелегко прочитать. В шестнадцатеричном формате это же значение будет выглядеть так: 3A7F 9826 . Это делает шестнадцатеричные значения полезными как краткий способ представления значения в памяти. По этой причине шестнадцатеричные значения часто используются для представления адресов или необработанных значений в памяти.
До C++14 не было возможности назначить литерал в двоичном формате. Однако шестнадцатеричные пары предоставляют нам для этого полезный обходной путь:
Литералы в двоичном формате и разделители цифр C++14
В C++14 мы можем назначать литералы в двоичном формате с помощью префикса 0b :
Поскольку длинные литералы трудночитаемы, в C++14 также добавлена возможность использования кавычек ( ' ) в качестве разделителя цифр.
Если ваш компилятор несовместим с C++14, он пожалуется, если вы попытаетесь использовать любой из этих приемов.
Печать десятичных, восьмеричных, шестнадцатеричных и двоичных чисел
По умолчанию C++ выводит значения в десятичном формате. Однако вы можете указать ему, что необходимо печатать в других форматах. Печать в десятичном, восьмеричном или шестнадцатеричном формате упрощается благодаря использованию std::dec , std::oct и std::hex :
Эта программа печатает:
Печать в двоичном формате немного сложнее, поскольку std::cout не имеет для этого встроенной возможности. К счастью, стандартная библиотека C++ включает в себя тип std::bitset (в заголовке <bitset> ), который сделает это за нас. Чтобы использовать std::bitset , мы можем определить переменную std::bitset и указать std::bitset , сколько бит мы хотим сохранить. Количество бит должно быть константой времени компиляции. std::bitset может быть инициализирован целочисленным значением без знака (в любом формате, включая десятичный, восьмеричный, шестнадцатеричный или двоичный).
Эта программа напечатает:
Мы также можем создать временный (анонимный) std::bitset для печати одного значения. В приведенном выше коде эта строка:
создает временный объект std::bitset с 4 битами, инициализирует его с помощью литерала 0b1010 , печатает его значение в двоичном формате, а затем отбрасывает временный std::bitset .
Магические числа, и почему это плохо
Рассмотрим следующий фрагмент:
Число, такое как 30 в приведенном выше фрагменте, называется магическим числом. Магическое число – это литерал (обычно число) в середине кода, не имеющий никакого контекста. Что значит 30? Хотя вы, наверное, догадываетесь, что в данном случае это максимальное количество студентов в классе, но это не совсем очевидно. В более сложных программах может быть очень сложно сделать вывод, что представляет собой жестко запрограммированное число, если нет комментария, объясняющего его.
Использование магических чисел обычно считается плохой практикой, потому что, помимо отсутствия контекста, для чего они используются, они создают проблемы, если значение необходимо изменить. Предположим, школа покупает новые парты, позволяющие увеличить количество учеников в классе с 30 до 35, и наша программа должна это отразить. Рассмотрим следующую программу:
Чтобы обновить нашу программу для использования нового размера класса, нам нужно будет обновить константу с 30 до 35. Но как насчет вызова setMax() ? Имеет ли это число 30 то же значение, что и другое число 30? Если да, то его следует обновить. В противном случае его следует оставить в покое, иначе мы можем сломать нашу программу где-нибудь еще. Если вы выполняете глобальный поиск и замену, вы можете случайно обновить аргумент setMax() , хотя он не должен был изменяться. Таким образом, вам нужно просмотреть весь код для каждого экземпляра литерала 30, а затем определить, нужно ли его изменить или нет. Это может занять много времени (и привести к ошибкам).
Хотя мы говорим «магические числа», это относится ко всем видам значений. Рассмотрим следующий пример:
В этом примере только одно число (100), но оно также используется в строках. Если мы решим обновить максимальное количество, скажем, на 200, нам придется обновить три разных случая, где встречается 100.
К счастью, существуют лучшие варианты (символьные константы). Об этом мы поговорим на следующем уроке.
Объектно-ориентированное программирование в ванильном JavaScript

JavaScript — мощный объектно-ориентированный (ООП) язык. Но, в отличие от многих других языков, он использует ООП-модель на основе прототипов, что делает его синтаксис непривычным для многих разработчиков. Кроме того, JavaScript работает с функциями как с объектами первого класса, что может путать программистов, не знакомых с этими концепциями. Можно обойти их, применяя производный язык вроде TypeScript, имеющий знакомый синтаксис и предлагающий дополнительные возможности. Но такие языки всё-равно компилируются в чистый JavaScript, и простое знание об этом не поможет вам понять, как они работают на самом деле, а также когда целесообразно их применять.
О чём мы поговорим в этой статье:
- Пространство имён.
- Объекты.
- Объектные литералы.
- Функции-конструкторы.
- Наследование.
Пространство имён
В сети появляется всё больше сторонних библиотек, фреймворков и зависимостей, поэтому определение пространства имён является необходимостью в JavaScript-разработке, если мы хотим избежать коллизий между объектами и переменными в глобальном пространстве имён.
К сожалению, JS не имеет встроенной поддержки определения пространства имён, но мы можем использовать объекты для достижения того же результата. Есть много разных паттернов для реализации, но мы рассмотрим только самый распространённый — вложенные пространства имён.
Этот паттерн использует объектный литерал, чтобы собрать функциональность в пакет и дать ему уникальное, специфичное для приложения имя. Можем начать с создания глобального объекта и присвоения его к переменной:
По той же методике можно создавать подпространства имён:
Сделав контейнер, мы можем использовать его для определения методов и свойств, а затем применять их в нашем глобальном пространстве имён без риска возникновения коллизий.
Подробнее о паттернах определения пространств имён в JavaScript можно почитать здесь: Essential JavaScript Namespacing Patterns.
Объекты
Если вы уже писали код на JavaScript, то в той или иной мере использовали объекты. JavaScript имеет три различных типа объектов:
Нативные объекты (Native Objects)
Нативные объекты — часть спецификации языка. Они доступны нам вне зависимости от того, на каком клиенте исполняется наш код. Примеры: Array, Date и Math. Полный список нативных объектов.
Хост-объекты (Host Objects)
В отличие от нативных, хост-объекты становятся нам доступны благодаря клиентам, на которых исполняется наш код. На разных клиентах мы в большинстве случаев можем взаимодействовать с разными хост-объектами. Например, если пишем код для браузера, то он предоставляет нам window, document, location и history.
Пользовательские объекты (User Objects)
Пользовательские объекты, иногда называемые предоставленными (contributed objects), — наши собственные объекты, определяемые в ходе run time. Есть два способа объявления своих объектов в JS, и мы рассмотрим их далее.
Объектные литералы (Object Literals)
Мы уже коснулись объектных литералов в главе про определение пространства имён. Теперь поясним: объектный литерал — это разделённый запятыми список пар имя-значение, помещённый в фигурные скобки. Эти литералы могут содержать свойства и методы, и как и любые другие объекты в JS могут передаваться функциям и возвращаться ими. Пример объектного литерала:
Объектные литералы являются синглтонами. Чаще всего их используют для инкапсулирования кода и заключения его в аккуратный пакет во избежание коллизий с переменными и объектами в глобальной области видимости (пространстве имён), а также для передачи конфигураций в плагины и объекты.
Объектные литералы полезны, но не могут быть инстанцированы и от них нельзя наследовать. Если вам нужны эти возможности, то придётся обратиться к другому методу создания объектов в JS.
Функции-конструкторы
В JavaScript функции считаются объектами первого класса, то есть они поддерживают те же операции, что доступны для других сущностей. В реалиях языка это означает, что функции могут быть сконструированы в ходе run time, переданы в качестве аргументов, возвращены из других функций и присвоены переменным. Более того, они могут иметь собственные свойства и методы. Это позволяет использовать функции как объекты, которые могут быть инстанцированы и от которых можно наследовать.
Пример использования определения объекта с помощью функции-конструктора:
Создание функции-конструктора аналогично созданию регулярного выражения за одним исключением: мы используем ключевое слово this для объявления свойств и методов.
Инстанцирование функций-конструкторов с помощью ключевого слова new аналогично инстанцированию объекта в традиционном языке программирования, основанном на классах. Однако здесь есть одна неочевидная, на первый взгляд, проблема.
При создании в JS новых объектов с помощью ключевого слова new мы раз за разом выполняем функциональный блок (function block), что заставляет наш скрипт КАЖДЫЙ РАЗ объявлять анонимные функции для каждого метода. В результате программа потребляет больше памяти, чем следует, что может серьёзно повлиять на производительность, в зависимости от масштабов программы.
К счастью, есть способ прикрепления методов к функциям-конструкторам без загрязнения глобальной области видимости.
Методы и прототипы
JavaScript — прототипный (prototypal) язык, то есть мы можем использовать прототпы в качестве шаблонов объектов. Это поможет нам избежать ловушки с анонимными функциями по мере масштабирования наших приложений. Prototype — специальное свойство в JavaScript, позволяющее добавлять к объектам новые методы.
Вот вариант нашего примера, переписанный с использованием прототипов:
В этом примере sayHey() будет совместно использоваться всеми экземплярами объекта User.
Наследование
Также прототипы используются для наследования в рамках цепочки прототипов. В JS каждый объект имеет прототип, а раз прототип — всего лишь ещё один объект, то и у него тоже есть прототип, и так далее… пока не дойдём до прототипа со значением null — это последнее звено цепочки.
Когда мы обращаемся к методу или свойству, JS проверяет, задан ли он в определении объекта, и если нет, то проверяет прототип и ищет определение там. Если и в прототипе не находит, то идёт по цепочке прототипов, пока не найдёт или пока не достигнет конца цепочки.
Вот как это работает:
У вас может уйти какое-то время на привыкание к прототипному наследованию, но важно освоить эту концепцию, если вы хотите достигнуть высот в ванильном JavaScript. Хотя её часто называют одним из слабых мест языка, прототипная модель наследования фактически мощнее классической модели. Например, не составит труда построить классическую модель поверх прототипной.
В ECMAScript 6 появился новый набор ключевых слов, реализующих классы. Хотя эти конструкты выглядят так же, как в основанных на классах языках, это не одно и то же. JavaScript по прежнему основан на прототипах.
JavaScript развивался долгое время, в течение которого в него внедрялись разные практики, которых по современным меркам нужно избегать. С появлением ES2015 ситуация начала медленно меняться, но всё же многие разработчики придерживаются всё ещё придерживаются старых методов, которые ставят под угрозу релевантность их кода. Понимание и применение в JavaScript ООП-концепций критически важно для написания устойчивого кода, и я надеюсь, что это краткое введение поможет вам в этом.