Содержание

JavaScript Essential. Видео урок «Функции»

Функции – это блок кода, который можно многократно вызывать для выполнения. Функция может принимать аргументы и возвращать значения. Я думаю, что многие из слушателей этого видеокурса сталкивались с такими понятиями как функции и процедуры. На всякий случай давайте мы попробуем еще раз с самого начала объяснить и понять, как работают функции, какое их назначение в программном коде. Функция по сути является основным строительным блоком любого приложения. Если вы посмотрите на этот слайд, то вот этот прямоугольник посредине слайда, он у нас символизируем функцию, то есть это некий черный ящик. Мы даем ему какую-то информацию на вход, в этом черном ящике происходят какие-то операции и что-то появляется на выходе, какой-то результат. Вот получается, мы в функцию передаем сейчас, вот эта иконка символизирует у нас один байт информации, вот со значением один в двоичной системе исчисления. Вот мы в функцию передали это значение, вот эти колесики, они символизируют какие-то действия, происходящие внутри функции, то есть функция произвела какие-то вычисления, что-то произошло с этим значением, и в итоге функция на выход дала какое-то новое значение. Бывают функции, которые принимают данные и выдают какой-то результат. Бывают функции, которые ничего не принимают и выдают какой-то результат. Бывают функции, которые ничего не принимают и ничего не выдают. Бывают функции, которые что-то принимают, но ничего не возвращают. То есть, есть несколько разновидностей функций, мы сейчас их в коде всех с вами разберем. Но перед тем, как переходить к коду, давайте попробуем понять назначение функций на каком-нибудь простом, жизненном примере. Представьте, что вам, в вашем приложении нужно описать процесс заварки чая. Вам нужно будет в JavaScript сценарии несколько раз в разных частях программы вашей выполнять заварку чая. Из чего состоит заварка чая? Первое – нужно пойти на кухню. Второе – нужно взять чашку и засыпать туда сахар и чай. Третья операция – закипятить воду. Четвертая – залить воду в чашку. Вот 4 операции и чай готов. Если вам нужно в вашем приложении чай заваривать 10 раз, вот вам придется в 10 разных частях программы вызвать 4 инструкции для того, чтобы заварить чай. При этом вам нужно следить, чтобы вы не напутали с последовательностью инструкций, потому что если вы в начале зальете воду, потом закипятите ее, это уже будет не совсем чай, неправильно он будет сделан. Получается, что в тех ситуациях, когда у нас есть несколько инструкций, какой-то блок кода, который повторяется в разных частях программы, вот этот блок кода можно превратить в функцию. Например, вот эти 4 инструкции, для заварки чая, их можно вынести в функцию и дать функции имя «Заварить чай». А те части программы, в которых мы раньше пытались заваривать чай, используя 4 инструкции, 4 команды последовательные, вот эти 4 команды можно удалить и вместо них установить запуск функции «заварка чая». Я верю, что после просмотра примеров, назначение функций всем будет понятней.

Давайте сейчас посмотрим второй слайд, посмотрим, как именно функция определяется в JavaScript коде, потом перейдем в Visual Studio и посмотрим на несколько первых примерах. Когда мы хотим определить в коде функцию, первое, что мы должны сделать – это использовать специальное ключевое слово. Слова function. Наличие этого слова в коде указывает, что мы сейчас создаем новую функцию, или объявляем функцию. После слова function в большинстве случаев следует имя. В некоторых ситуациях имя не указывается, мы рассмотрим такие варианты функций, но сейчас на слайде вы видите, что сразу после function, тут же идет фраза, литерал, который будет указывать имя функции, обычно функцию называют не так, как на нашем слайде, myFunction – это имя, которое ничего не означает. По имени не можем понять, а что именно делает функция. Хорошее имя для функции – это глагол, описывающею что именно функция будет выполнять, когда мы ее вызовем. Например, функция будет называются getUserName. Сразу же понятно назначение функции. Если мы ее запустим, то она отдаст нам имя пользователя. Или спросит имя у пользователя, или спросит имя у пользователя, или получит его с каких-то внутренних переменных. Другой вариант функции – Calculate, CalculateSum, например, то есть посчитать сумму. Понятно, что если мы эту функцию запустим, то получим сумму каких-то значений. После имени функции, у нас установлены параметры, параметры – это то, что функция принимает для обработки. Если нашей функции при запуске необходимы какие-то значения, эти значения функции могут быть переданы с помощью параметров. Подробней с параметрами мы познакомимся с вами уже в примерах.

Давайте сейчас посмотрим первые примеры использования функции в Visual Studio. Значит, как мы уже посмотрели на слайдах, определение функции всегда начинается с ключевого слова function. Вот на строке 21 мы создаем функцию, создаем приложение, создаем строительный блок, который будем потом неоднократно использовать. Мы указали ключевое слово, далее мы указали имя функции – MyFirstFunction(). Так как эта функция не принимает никаких входящих данных, она не должна производить расчеты над какими-то значениями, мы просто после имени ставим круглые скобочки. Это означает, что функция не принимает никаких параметров. И далее в теле функции мы выполняем три инструкции, в тело документа выводим сообщение «Hello from MyFirstFunction!, How are you?, Goodbye!». Вот эти три строчки появляться в документе если мы запустим эту функцию. Мы сейчас просто создали функцию, то есть это просто заготовка, которую потом нужно вызвать, выполнить. Получается, когда интерпретатор, когда браузер дойдет до 21 строки. Существует такая функция, он не будет выполнять этот код, потому что определение функции, это просто создание инструкций, которые в будущем кто-то начнет использовать. Так же в нашем примере есть еще одна функция, на 27 строке, эта функция называется MySecondFunction(), и она выводит только одно сообщение «Hello from MySecondFunction!». Это все что она делает. Точно так же, как и предыдущая функция, это всего лишь объявление. То есть это блок кода, который не будет выполнятся до тех пор, пока мы не обратимся к функции и не запустим ее, не вызовем функцию. Значит 21-22 строка – мы объявили функцию. Теперь как мы этими функциями пользуемся. 33 строчка. Мы берем имя функции MyFirstFunction() уже без ключевого слова function, только имя и круглые скобочки. Когда мы используем такой синтаксис – имя функции + ее параметры, это означает, что мы обращаемся к функции и вот на строе 33, как только мы выполним эту операцию, это означает, что с 33 строки мы перепрыгиваем на строку 21. Выполняем инструкции на 22, 23, 24 строчке, и потом, когда мы эти инструкции до конца выполняем, мы возвращаемся обратно, перепрыгиваем на 33 строку и продолжаем выполнять инструкции, которые идут вот в этой части кода. Выполняем 35 строчку, вводим горизонтальную линию. Потом на 37 строчке мы видим, что мы взываем вторую функцию MySecondFunction(). То есть на 37 строке мы прыгаем на строчку 27, заходим в тело функции, вот операторные скобки, выполняем инструкцию, которая здесь заложена. Как только мы до конца выполняем все инструкции, заложенные в функции, мы возвращаемся обратно в ту точку, где функция изначально была запущена. Давайте попробуем проверить, что получится. Вот вы видите, первый раз мы на 33 строке вызвали функцию и отобразились те сообщения, которые заложены внутри этой функции, вот все эти инструкции, код весь этот выполнился. Потом 35 строчка, мы вывели горизонтальную линию. И строка 37 мы еще раз запустили функцию, уже вторую функцию и вот увидели ее результат. Если мы хотим несколько раз повторить вот этот блок кода, несколько раз вывести эти три сообщения, можем взять и просто продублировать вызов функции. Вот мы сейчас три раза запускаем MyFirstFunction(), то есть в приложении, на 33 строке мы пригнем на строку 21, выполним все что заложено в функции, вернемся обратно сюда. Перейдем на 34 строчку и увидим, что на ней опять происходит вызов функции. На 34 строке мы пригнем на 21 строчку, выполним все, что заложено в функцию и вернемся обратно на 34. На 35 опять пригнем в функцию, выполним все, что заложено и вернемся на 35. То есть каждый раз, когда мы вызываем функцию, мы производим переход в другую часть программы, выполняем инструкции, заложенные в этой другой части программы и возвращаемся обратно, где функция была изначально запущена. Давайте сейчас запустим и проверим, первый раз вызвалась наша функция, второй раз и третий раз. Вот пример, который я приводил с заваркой чая. Вместо того, чтобы здесь дублировать инструкции заварки чая, мы эти инструкции вынесли в отдельную функцию и каждый раз, когда нам хочется чая, мы берем и запускаем функцию сделать чай и не задумываемся о тех шагах, которые нужно выполнить, для того, чтобы появилась чашка чая. Перед тем, как переходить к следующим примерам, хочу, чтобы мы еще посмотрели, как можно пользоваться отладчиком и посмотреть, как функции работают именно под отладчиком. Лично мне удобней пользоваться отладчиком, который работает в Google Chrome, но как мы уже разбирали на прошлых уроках, если вы возьмете Firefox, например, с установленными Fireback флаером, или Internet Explorer, или Opera, во всех современных браузерах есть инструменты разработчика, поэтому просто выберете тот браузер, которым вам пользоваться нравится больше всего и в нем попробуйте разобраться с инструментами разработчика. Если мы сейчас запустим этот пример, в Chrome, если мы нажимаем на F12 в браузере это открывает панель разработчика. Вот панель разработчика, нас интересует сейчас вкладочка source. Вот когда вы впервые запустите свое приложение, вы увидите, что вот на этой вкладочке ничего нету, будет пусто, никаких исходных кодов текущего документа отображаться не будет. Если нажать по этой кнопочки showNavigator, то вы увидите те сценарии, которые доступны сейчас в текущем документе. Мы выбираем сейчас сам HTML документ, переходим в его исходный код, находим интересующею для нас функцию, вот мы хотим начать отладку с 33 строки. Ставим здесь breakpoint. Мы на прошлых занятиях разбирали с вами что такое breakpoint. Поставили breakpoint. Для того, чтобы теперь этот breakpoint сработал, чтобы эта точка остановила код и показала нам пошаговое выполнение скрипта, мы можем сейчас обновить страницу, видите сейчас вот это синий блок, красная стрелочка, которая находится рядом с ним, эта стрелочка символизирует ту часть кода, которую сейчас браузер выполняет. Вот сейчас мы возьмем с вами и если будем нажимать на кнопку F10 или на эту иконку, это означает, что мы переходи пошагово, построчно. Просто выполняем все инструкции. Если мы будем нажимать на кнопку F11 или на эту иконку, это будет означать, что при вызове функции мы будем переходить внутрь функции и смотреть, что будет происходить в самой функции. Вот если мы сейчас нажмем на F11, так как вот здесь мы взываем функцию myFunction, мы должны перейти внутрь функции, выполнить весь код, который находится в функции и вернутся обратно на 33 строчку. Давайте попробуем. Нажимаем на F11, видите мы попали внутрь функции, на строчку 22. Нажимаем еще раз F11, еще раз, и вот когда мы закончим операцию на 24 строке, мы вернемся в ту часть кода, в которой функцию изначально вызвали. Нажимаем еще раз F11 и вот видите мы вернулись на строчку 34 из-за функции, которую изначально запустили. Если сейчас еще раз нажать F11. Так как на 34 строке опять запускается функция, мы вернемся в тело функции и выполним то что находится в теле функции. Нажимаем F11, попадаем в функцию, выполняем код, который заложен в функции, видите, код у нас выводится сразу же в самом документе по мере его выполнения, выполнили функцию, вернулись в ту часть кода, где функция запускалась. Следующая функция, нажимаем F11, попадаем в тело функции, и вот так далее. Вот если вы не понимаете, как работает сценарий, если вы хотите найти ошибки в сценарии, рекомендую пользоваться отладчиком, он всегда помогает сэкономить время и быстро найти ту часть кода, с которой что-то у вас работает некорректно.

В следующем примере мы увидим, как в функции можно использовать переменные, которые доступны в области видимости, глобальной области видимости. Подробней о областях видимости мы сейчас с вами поговорим, а вот пока разберем данный пример. То есть в этом примере мы разберем, что функции могут работать с переменными в своем теле. Сейчас у нас есть 2 функции, на 22 строчке ask и на 28 функция say. То есть «спросить» и «сказать». Функция ask будет запускать ряд prompt функций, то есть функция prompt, это функция, которая уже определена в браузере. То есть если вот эта функция, это основная функция, то функция prompt находится где-то в браузере, задача этой функции показать диалоговое окошко, получить от пользователя данные, обработать эти данные и вернуть в наше приложение, вернуть нашему сценарию уже. Если кто-то запустит функцию ask в нашем коде, то вначале мы получим от пользователя имя, потом фамилию, потом возраст. И эти полученные данные запишем в переменную name, sname и age. Переменные, которые мы используем в функции ask обвялены выше, на 18, 19, 20 строчке. Вот эти переменные объявлены здесь и соответственно они могут быть использованы как внутри функции ask, так и внутри функции say. Видите, что функция say будет последовательно отображать вначале имя, потом фамилию, потом возраст и выводить эту информацию в тело документа. Но как мы уже с вами обсуждали, определение функции не значит, что эти функции будут работать, просто определив функцию, мы определили строительный блок в нашем приложении. Тут этот строительный блок должен использовать, для того чтобы код в функции запустился. И вот на 37 строке мы первый раз вначале запускаем функцию ask, чтобы попросить пользователя заполнить значениями переменные. А второй раз на 38 строчке мы запускаем функцию say, чтобы значения, которые находятся в переменных отобразились пользователю на экран. Давайте запустим пример и проверим. Первое – введите свое имя, фамилию и возраст. Вот значения, которые мы ввели в поля ввода, вот они отобразились у нас на экране.

В следующем третьем примере мы по сути разбираем второй пример, который переделан на использование в цикле. То есть мы здесь будем неоднократно задавать пользователю вопрос, для того, чтобы как бы повторно выполнить несколько операций, несколько функций. Вначале сценария определяются те же функции, что и в предыдущем примере, но вот на 34 строке запускается цикл do..while, помните, что в цикле do..while у нас всегда выполняются первая итерация, а потом, в зависимости от условия мы либо продолжаем работать, либо повторяем итерацию цикла, либо выходим из цикла и продолжаем инструкции. Каждый раз, когда будет запускается цикл do..while, на каждой итерации мы будем запрашивать имя, фамилию, возраст. Отображать полученные данные, а потом на 38 строчке в переменную repeat записывать значения, которые пользователь выберет в диалоговом окошке с кнопками «Да» и «Нет». То есть мы спросим «Пройти заполнение еще раз?». Если пользователь нажмет на «Да», то в переменную repeat запишется значение true и мы на строке 38, условие у нас срабатывает, поэтому мы возвращаемся на начало и еще раз выполняем инструкцию. Если же пользователь выберет значение «Нет» — cancel нажмет, то repeat будет false, условие не срабатывает и наш сценарий выходит дальше, ну дальше нет инструкции, поэтому выполнение кода завершается. Давайте проверим. Вот «Введите свое имя, фамилию, возраст» и теперь смотрите, мы первый раз вывели, запустили первую-вторую функцию, ask и say. Теперь запускается функция confirm, которая спрашивает повторить или нет, если мы нажмем повторить, мы еще раз запускаем повторную функцию ask, она у нас спрашивает возраст и фамилию, потом нажимаем на Ок. Теперь, если мы нажмем на cancel, то у нас спрашивает повторно хотим ли мы заполнить эту анкету, если мы нажимаем на cancel, то цикл прекращается, повторно функцию мы уже не запускаем и прекращаем генерировать какой-то контент, в текущем документе. Вот получается, что использование функции, они даже упрощают понимание самого кода, нам не приходится вдавятся в детали, в подробности, мы можем просто запомнить, что делает каждая из этих функций и просто проанализировать алгоритм работы самого сценария. Вот у нас есть задание вопросов, потом что-то мы выводим пользователю, потом спрашиваем нужно ли повторить и выполняем повторение, если пользователь согласится с повтором. Но если бы у нас функции не было, то все, что находится внутри, детали реализации задания вопросов, детали реализации вывода результатов в документ, все эти детали находились в самом цикле do, и соответственно цикл был бы достаточно большим, достаточно сложно было бы его редактировать и контролировать, а так, когда у вас есть отдельные функции и у функции выражены четко обязанности функции, то вы может, если вы знаете, что нашли ошибку, в той части кода, в котором задают вопрос, то сразу понимаете куда нужно обратится, где нужно искать ошибку. Нужно открыть функцию ask и посмотреть, как она работает. Если вы зафиксировали ошибку в выводе пользователю данных, вы берете функцию say и проверяете, что в этой функции было написано неправильно. Если же у вас функции нету и все выполняется единым блоком кода, там несколько сотен строчек, то в таком случае вам нужно будет ковыряться вот в всем этом коде и искать где там что происходит. Поэтому функции, они очень часто помогаю сделать код более логическим, структурированным, сделать так, чтобы его потом проще сохранить. Если вам нужно пользоваться функциями, то сценарий становится упорядоченным и дальше с ним будет намного легче работать. В следующих примерах мы посмотрим, как можно задавать функциям аргументы. Как можно сделать функцию, которая при запуске будет получать от пользователя какие-либо данные. Как мы уже говорили, параметры функции определяются после имени функции, вот на 22 строке функция, как и в предыдущих наших примерах не принимает никаких параметров или аргументов. Аргументы и параметры – это одно и тоже самое. Вот на 20 строчке функция не принимает аргументов, а на строке 25 функция принимает 2 аргумента или 2 параметра. Первый параметр с именем х1, второй параметр с именем х2. Давайте пока разберем первую функцию, что она делает. В функции самм1 на строке 21 создается переменная result и в эту переменную записывается сумма двух других переменных, которые определены выше, на строке 15 и 16. Получается, что в переменную result будет записана сумма 25. И эту сумму на 22 строке мы отобразим в тело документа. На строке 26, когда мы разбираем эту функцию, мы не можем заранее знать какой результат будет отображается этой функцией, потому что при загрузке функции — вот эти переменные х1 и х2, в эти параметры могут быть заданы какие-либо значение. Какие именно мы сейчас не знаем, потому что выше таких переменных х1 и х2 у нас не объявлено. Получается, что кто-то запуская эту функцию передаст сюда значение. Например, в х1 передаст значение 1, в х2 значение – 4, например. Вот здесь на 26 строчке 1 + 4 даст результат 5. Этот результат будет записан в переменную result и в итоге отображен на 27 строке. Вот мы увидели 2 объявление функции. Давайте теперь разберем как эта функция запускается. Разницу вызова. На строке 33 мы берем sum1 и {} после sum2. Этот вызов правильный, потому что сама функция ничего не принимает, не требует никаких от пользователя значений. Под пользователем сейчас подразумевается разработчик, который запускает эту функцию. И вот сейчас на 33 строке у нас запускается функция и на экране мы выводим результат, результат 25. На строке 25 мы запускаем функцию sum2. И тоже не переедаем ей никаких параметров, это у нас не правильно потому что функция sum1 при определении требует два аргумента х1 и х2. Если мы не будем при вызове передавать никаких значений, то это будет означать, что х1 и х2 будут неопределенны. А мы уже с вами знаем, что когда будет, когда создаем переменную и не указываем значение для этой переменной. У переменной значением по умолчанию будет undefined. Давайте посмотрим, что получится если запустить такую функцию. Видите, первый раз мы увидели 25, когда вызвали sum1, а второй раз, когда мы вызвали саааамммммм2, мы результат увидели, как none. Потому что undefined + undefined получается непонятно что, not a number. Вот мы выводим этот результата. Вот когда на 36 строке мы запускаем функцию sum2, мы уже пользуемся ею корректно, мы передаем ей два значение, мы указываем, что первое значение будет значение переменной “c”. Переменная “c” у нас находится на 17 строке, в ней находится значение 20. Нас троке 36 у нас находится значение переменной “d”. Это значение придется как второй аргумент. В переменной “d” находится значение 21. Сейчас, на 36 строке, когда запускается эта функция, мы переходим на 25 строчку, здесь в х1 находится значение 20, в х2 находится значение 21. Соответственно сумма 21 и 20 дает результат 41, который мы выводим на экран. Видите, 41 отобразилось у нас в документе. При запуске функции необязательно передавать значения, которые взяты из переменных, мы можем прямо здесь установить значение 2 и 10, например. Вот сейчас, если мы запустим вот такую функцию, то результат, который выведется в документ, будет равен 12. Проверяем. Получилось 12. Давайте еще раз посмотрим, как это будет выглядеть в отладчике. Нажимаем F12, берем source, navigator, выбираем текущий документ, переходим к той части кода, которая нас интересует, вот нас интересует сейчас 36 строка. Ставим здесь breakpoint, нажимаем refresh, вот мы остановились на этой функции, остановили ее, давайте нажмем сейчас step_into, или F11, мы перепрыгнули функцию, если сейчас мы наведем мишкой на х1, Chrome подскажет, что в этой переменной значение 2, в х2 – значение 10. В соответствии с теми значениями, которые мы передавали при вызове. Эти значения попали в эти два параметра, или в эти два аргумента и теперь на 26 строке мы эти значения сложили, можем сейчас нажать stepinto или stepover, сейчас без разницы, потому что эта строчка не является вызовом функции. Давайте нажмем на F10, и переходим на следующею строку. Если наводим на result, видим, что Chrome подсказывает, что значение переменной 12. Значение мы дальше отображаем в тело документа. Его выведем сейчас. Вот 12 у нас отобразилось. Не забывайте пользоваться отладчиком, когда вы чего-то не понимаете, старайтесь с помощью отладчика выяснить что происходит внутри сценария.

В следующем пример мы посмотрим, как можно сделать так, чтобы функция требовала, чтобы одни параметры обязательно к ней были заданы, а другие параметры были опциональны. Вот сейчас на 15 строчке мы хотим сделать так, чтобы функция print принимала два аргумента. Первый аргумент- был обязательно, а второй аргумент, если его не предоставят, то функция возьмет свое какое-то значение по умолчанию. Задача функции print отображать в документе несколько сообщений, сообщений, которые были переданы первому аргументу. Вот если кто-то запускает эту функцию, первое, что делает функция – это 17 строчка. Проверят было ли предоставлено значение в параметр count. Если count равен undefined, то строка 18 мы говорим, что count будет равен значению три. То есть по умолчанию у нас всегда функция принт будет с count равен трем. Но если при запуске сюда передать значение, то на 17 строке условие не срабатывает и count будет равен тому значению, которое передал пользователь при вызове. Дальше после проверки на 21 строке мы запускаем цикл фор на количество итераций, равной значению переменной count. То есть count раз, на 22 строчке мы выводим сообщение переменной msg, то есть message. А в конце, когда все это заканчивается на 25 строке мы выводим horizontalRool. Вызов функции на 31 строчке, мы вызываем функцию только с один значение, со значением Hello, это значение первое, соответственно оно адресовано первому аргументу, переменной msg. НА 31 строке мы увидим, что сообщение Hello отобразится три раза, потому что count по умолчанию будет равен трем. На 32 строке, когда мы вызываем print двумя параметрами, двумя значениями world и семь. Это означает, что msg будет world, count будет равен семи. Соответственно условие не срабатывает и цикл срабатывает семь раз со значением, которое ввел пользователь. Hello три раза в соответствии со значением по умолчанию, world – семь раз. Вот этот пример показывает, как можно создать функцию и определить в функции опциональные параметры, которые не обязательны при запуске. Очень часто разработчики, которые создаю функцию с функциональными параметрами, добавляют вот такой комментарий, просто пишут необязательный параметр, или опциональный параметр для того, чтобы было понятно, что эта переменная, этот аргумент, он не обязательный при вызове. Все функции, которые мы сейчас с вами посмотрели, по сути они были процедурами, вот если вы работали с другими языками программирования, то вы сталкивались с таким термином как функция и процедура. Функция – это конструкция, блок кода, который мы запускаем, блок кода что-то делает, а потом возвращает нам результат. А процедура – это такой же блок кода, который мы запускаем, что-то происходит в этом блоке кода, но в результате ничего нам как клиентам этого блока кода, как запускающей стороне, нам никакой результат е возвращается. Вот мы с вами видели, что все наши примеры, в этих примерах функции не возвращали никаких данных в ответ. Мы их запустили, а в ответ ничего не получили. На самом деле в JavaScript нет такого понятия как процедуры, потому что любая функция всегда возвращает нам значение. Вот сейчас, у нас у всех примерах любая функция всегда возвращала значение undefined, просто мы им не пользовались и его игнорировали. Но запомните, что любая функция, если она не возвращает явно никаких данных, она вернет значение undefined. Давайте сейчас поострим как можно сделать функции, которые будут возвращать то что они рассчитали, то что они выполнили по окончанию своей работы.

Вот в следующем №6 примере мы посмотрим, как используется ключевое слово return и что оно делает. На строке 16 мы объявляем функцию sum, которая будет суммировать два входящих параметра «а» и «б». Но мы хотим сделать так, что функция, получив значение и посчитав сумму, не выводила сразу же результат в тело документа, а возвращала результат, которой получила. Мало ил как мы захотим с этим результатом работать, может мы не хотим его отображать в документе, может мы alert вывести, или мало что можно еще делать. Для того, чтобы функция просто вернула результаты своей работы, на 17 строке мы используем ключевое слово return, и после этого слова указываем то значение, которое функция должна будет вернуть после своей работы. Если запустить функцию sum, она у нас выполнит вычисления, отдаст результат своих исчислений, отдаст сумму двух входящих параметров. Вот когда мы пользуемся такими функциями, строка 20, нам необходимо использовать функции вот в таком ключе, таким синтаксисом. Мы создаем переменную, а потом эту переменную инициализируем результатом работы функции. Если мы запускаем любую функцию, которая есть в JavaScript коде, эта функция всегда что-то возвращает, и это возвращаемое значение мы можем присвоить переменной. Вот сейчас возвращаемое значение функции sum будет суммой двух аргументов, два и три. Поэтому в переменную рез запишется результат 5, результат на 22 строке мы отобразим. Давайте проверим. Вот отобразилось значение 5. Если мы сейчас возьмем и, например, поменяем 18 строчку, наберем ключевое слово return, функция у нас уже будет выполнять возврат значений. Теперь это значение будет undefined, потому что это значение по умолчанию. Если разработчик не указал, что функция возвращает, то функция всегда будет возвращать undefined. Давайте запустим. Видите, результат работы функции undefined. Поэтому если вы не написали return, или ключевое слово return просто не выполнилось в теле функции, это означает, что функция вернет значение undefined. И еще, то что вы должны понимать, ключевое слово return прекращает выполнение функции. Если после этого ключевого слова мы напишем сообщение, это сообщение у нас не выведется, потому что 17 строка, как только интерпретатор видит ключевое слово return и выполняет его, то вот на этом ключевом слове у нас и заканчивается работа функции. Мы из 17 строки перепрыгиваем в ту точку кода, где функция изначально запускалась, тот результат, который ключевым словом return был возвращен, мы записываем в переменную. Вот вернулась 5, а alert так и не отобразился.

Вот теперь небольшой такой пример с использованием нескольких функций, которые возвращают результат. Сейчас мы определили 4 функции, функция add, для сложения, функция sub, для получения разницы, mul – для умножения и div – для деления. В каждой функции мы выполняем арифметическую операцию и результат возвращаем с помощью ключевого слова return. После определения функций, на строке 32 от пользователя с помощью prompt мы получаем введите первое число, то есть первую цифру, операнд один, потом мы получаем знак операции, + — * / строка 34. Потом получаем операнд два, второе значение. И 35 строчка – создаем переменную, которая будет просто содержать в себе результат работы наших дальнейших вычислений. На 37-38 строке мы используя функцию parseInt, конвертируем значение, которое вводил пользователь в целочисленное, для того, чтобы все арифметические действия выполнялись как операции над числами, а не операции над строками. На 40 строчки создаем switch, для того, чтобы проверить какое значение было введено с помощью prompt на 33 строке. Если пользователь выбрал +, то 42 строчка в переменную result мы записываем значение, которое возвращает функция add, мы вызываем функцию, определенную выше, передаем в эту функцию два параметра, которые были получены от пользователя на 37-38 строке. И результат, который получится — записываем в переменную result, на 43 строчке у нас выполняется break, поэтому после этой строки мы выпрыгиваем за switch и отображаем надпись од 7 строки. Если у нас результат не undefined, потому что, мало ли мы не попали ни в один из case, если мы вот с брейка перепрыгнем сюда, то вот тогда мы отобразим, что вот такой операнд + символ + второй операнд = значение. Если мы сейчас выберем, например, нажмем не на +, вот здесь в переменную sun поместим не +, а допустим значение «*» — умножить, то тогда на 40 строчке, когда будет работать switch, мы перепрыгнем на строку 48, на 48 строчку, выполним этот блок кода. Выполнится уже другая функция, которая получит 2 параметра и получит произведение 2 значений, это значение здесь запишется в result и здесь запишется. Давайте проверим как будет работать этот код. Введите первое число. Вводим два. Введите знак арифметической операции – вводим «+» и второе число – тоже два. Вот результат «2+2=4». Вводим 5*12, получается – 60. Вот такой простенький калькулятор написанный на JavaScript.

В следующем примере мы разберем пример с использованием нескольких аргументов. Если копнуть немножко глубже, то на самом деле функция в языке JavaScript – это не просто как бы синтаксическая единица, это тип данных. Если вы работали с языками там С++, Pascal, C#, любыми языками программирования, в большинстве языков функция это именно вот синтаксическая конструкция, это часть языка, с помощью которой мы определяем блок кода для повторного использования. А вот в JavaScript – функция – это тип данных и любая функция – это на самом деле – объект. Вот сейчас пока вы можете рассматривать функции как код для повторения, но если вы будут дальше работать с JavaScript кодом, если вы будут прослушивать следующий курс JavaScript углубленный, то вы там будете очень часто сталкивается вот с тем, что функция – это объект, а не просто кусочек кода. И вот сейчас мы тоже с вами увидим вот эту особенность функции в JavaScript коде, увидим, что функции, как объекты содержат в себе какие-то свойства. То есть функция кроме кода, который нужно выполнить, также в себе содержит разные значение, которые могут помочь создавать сложные и интересные сценарии. Сейчас у нас на 16 строке есть функции max, задача этой функции показать нам максимально значение того аргумента, который был передан при вызове. Вот если мы запустим функцию max и передадим в «а» значение 1, в «и» значение 10, а в «с» значение – 100, то функция вернет то значение, которое максимальное. Значение параметра «с», потому что там была записана сотня. Но также мы хоти сделать так, чтобы функция принимала у нас не только три параметра, а допустим 5, 10, 20, то есть неограниченное количество параметров. И в JavaScript это сделать очень легко, мы можем сделать так, чтобы функция принимала хоть 50 параметров, нам в принципе все равно. На 18 строке, когда эту функцию запустят, мы создадим переменную maxValue, вот в этой переменной будет находится максимальное значение, которое мы найдем в параметрах. Но изначально максимальным значением мы считаем минимальное значение, которое вообще может быть записано в целочисленную переменную. Мы записываем минус бесконечность. И потом посредством цикла, посредством проверок, мы это значение будем менять. На строке 19 вот мы изначально выводим какое значение записано в переменную maxValue, изначально мы записали NegativInfinity, то есть минус бесконечность. После вывода этого сообщение, на строке 21 запускается цикл и обратите внимание, на какое количество итераций. Мы берем переменную arguments и берем ее свойства length. Откуда взялась переменная arguments, если посмотреть вот в текущем коде, нигде такую переменную мы с вами не определяли. Вот как раз эта переменная, она у нас доступна сейчас в данном блоке кода, потому что функция – это не просто кусочек кода, это не синтаксическая единица языка JavaScript – это объект. Вот когда мы, вот сейчас выделенный код, который вы видите у себя, это есть код, который находится внутри объекта с именем макс внутри функции. И вот у объектов функции есть несколько свойств. Одно из свойств – это свойство arguments, которое в себе содержит все аргументы передаваемые функции при запуске. Вот сейчас, если мы запусти эту функцию вот таким вот способом, на строке 28 видите мы передаем 1, 2, 3, 4, 5. Пять значений. И вот получается сейчас, когда эта функция начинает работать, в переменной «а», «b», «с» находится -33, 33, 777. А вот остальные значения, они у нас не представлены параметрами, но они все равно доступны в функции, они как бы доступны функции через массив arguments. Массив arguments – это все значение, которые пришли в функцию при вызове. Вот сейчас в этом массиве, кроме первых трех значений, которые доступны так же через параметры, находится еще и два дополнительных значения, -666 и -22. Вот сейчас мы запускаем цикл на 21 строке, и на каждой итерации проверяем. Если arguments, вот как раз предыдущий урок, мы смотрели массивы, если и-тый элемент массива, на первой итерации – это -33. Если он больше, чем maxValue, а 33 будет больше чем минус бесконечность в любом случае, то есть если больше, то тогда 23 строчка, максимальное значение – оно у нас будет равно текущему аргументу – из массива arguments. То есть в maxValue мы записываем -33. Потом следующая итерация, мы берем следующий элемент из массива arguments 33, 33 больше чем maxValue? maxValue сейчас у нас -33, соответственно положительное 33 больше чем -33. maxValue теперь у нас новое значение, значение 33. Следующая итерация, здесь у нас 777, здесь 33. 777 больше чем 33, поэтому maxValue теперь у нас 777. Следующая итерация, здесь у нас находится -666, а maxValue у нас три семерки. Три семерки больше, условие не срабатывает, в условие мы не попадаем и maxValue не изменяем. То есть в итоге в переменной maxValue останется максимальное значение, которое при запуске было передано в параметры. Вот это максимально значение, которое по завершению этого цикла мы получим, это значение на 25 строке мы вернем с помощью ключевого слова return. И на 28 строчке видит, в переменную rez мы записываем тот результат, который был возвращен в функцию. Ну на 33 строке мы этот результат показываем в документ. В нашем случае, максимально значение сейчас будет 777. Вот это значение у нас вывелось. Если мы добавим еще несколько параметров, вот сейчас максимальное значение будет у нас 1000, это максимальное значение на 30 строке отобразится. Вот отобразилась 1000. В этом примере, что показывает этот пример, то что вам нужно запомнить, создавая функцию, вы можете сделать так, чтобы функция принимала неограниченное количество параметров, и вы в функции могли получать доступ ко всем этим параметров. Для этого вам необходимо пользоваться свойством arguments, которое доступно в теле функции. За пределами функции, это свойство у вас будет недоступным. Но когда вы работаете в этой функции, посредством этого свойства вы можете получать доступ ко всем параметрам, которые были переданы в функцию при запуске.

И последний, девятый пример из первой директории, показывает, как мы можем сделать функцию, которая будет проверять правильное ли количество параметров передали функции при запуске. Иногда нам важно, чтобы запускаемая функция принимала столько аргументов, сколько было указано при определении функции. Вот в предыдущем примере мы специально сделали так, чтобы функция принимала неограниченное количество параметров. Но сейчас мы хотим сделать именно так, чтобы в функции у нас принималось ровно три параметра, ни меньше, ни больше. Если количество параметров не совпадает с требованными, то наша функция вообще никаких операций не будет производить. Определяем функцию мы на 16 строке. Видите, что функция принимает скорей всего три координаты: х, у, z. На 19 строчке у нас идет проверка. Если arguments.lenght не равно 3, то выводим сообщение о ошибке. И больше по сути ничего не делаем, ведь этот блок кода у нас не срабатывает. Если же arguments равно 3, то условие на 19 строке не сработает, сработает блок else, увидим сообщение, что у нас корректно были получены данные и строка 25, запустив цикл for, все значения, который были получены в параметрах мы отобразим на экран через запятую. Вот посмотрите теперь на то как мы функцией пользуемся. На 30 строке мы запускаем функцию с именем f, то есть вот эту функцию и передаем 4 значения при запуске. Четыре значение — это перебор, наша функцию требует три значения, это условие на 19 строке срабатывает и выводит ошибку. А вот на строке 31, когда мы передаем три значения, функцию получив эти три значения не запускает if, попадает в блок else и соответственно срабатывает у нас как бы сама логика функции. Ну и 32 строчка, тоже функцию не срабатывает, так как количество параметров у нас меньше трех. Первый раз – ошибка. Четыре аргумента было получено, второй раз все нормально, вывелись все элементы, третий раз опять ошибка – у нас меньше аргументов чем надо, было получено два аргумента. Вот в первой части урока мы рассмотрели с вами как создаются функции, как сделать так, чтобы функция возвращала результат своей работы, как сделать так, чтобы функция принимала входные параметры и как можно сделать функцию, которая работает с неограниченным количеством параметров, для этого в функции есть свойство arguments. Эти примеры обязательно хорошо рассмотрите, изучите, потому что это основа работы с функциями.

Вторая часть урока будет посвящена областям видимости. Перед тем, как перейти к областям видимости мы повторим с вами что происходит с переменными, которые не создавали. Нам пригодится дальше для того, чтобы понимать следующий пример. На 13 строке мы создаем переменную «а». Помните, что если переменной мы не присваиваем значение, то значение этой переменной при чтении будет у нас undefined. Для остальных переменных мы задаем конкретное значение и соответственно переменные у нас инициализируются и получают какой-то определений тип данных. Переменная «b» со значением 777 она у нас будет number. На строке 15 переменная «c» использует значение переменной «b», соответственно это тоже будет number и тоже со значением 777. Переменная «d» будет типа null и содержать в себе единственное возможное для типа null значение null. Создав эти переменные мы отображаем их значения на экран и выводим все те значение, которые тут мы определили. Если же мы попытаемся получить доступ к переменной, которую не создавали, вот как на строке 24, то эта строчка кода становится ошибкой. На этой строчке интерпретатор прекращает интерпретировать все, что мы определили в сценарии и код находящийся ниже просто прекращает работать. Поэтому 24 строчка и все строчки кода, которые мы могли бы написать ниже, они не перестанут работать. Давайте проверим. Вот первая переменная undefined, потом обе переменные «b» и «c» со значением 777, переменная «d» – null. Последняя переменная – она у нас отсутствует. Если мы чтобы проверить, что действительно весь код перестает работать, если мы этот код поставим в начало, интерпретатор первое дело, что сделает – это выполнит эту строку, получит здесь ошибку и не сможет выполнять код идущий далее. Давайте запустим. Видите, в таком случае у нас вообще ничего не отображается. Поэтому следите за использованием переменных, не используйте переменные если вы их не определяли. То есть если мы хотя бы создали переменную, как только мы ее с помощью ключевого слова var определили, эта переменная остается, тип этой переменной undefined, значение этой переменной undefined. Дальше мы эту переменную можем спокойно использовать. А вот если вы пытаетесь читать переменные, которых нету, вот к чему это приводит. Приводит к ошибке.

Теперь давайте посмотрим, что такое область видимости. В следующем примере мы разберем с вами понятие глобальной и локальной области видимости. В языке JavaScript есть только две области видимости. Глобальная – это область видимости, где работают все наши сценарии. И локальная область видимости. Это область видимости, отдельной функции. Если вы работали в языке C# или С++ у вас есть глобальная область видимости и локальная область видимости, которая определяется функцией, которая определяется условием, циклом. В языке JavaScript таких вложенных областей видимости просто не существует. Либо у нас глобальная область видимости, либо область видимости функции. Если мы собираемся создать переменную глобальную, которая будет доступна абсолютно всем сценариям текущего приложения, текущего документа, нам нужно просто создать переменную в теге <script>. Если мы создаем var global, эта переменная становится глобальной, потому что она не находится внутри функции. Если мы определяем функцию и создаем переменную с ключевым словом var внутри функции, то эта переменная будет доступна только в теле этой функции. Получается глобальная область видимости, вот она, вот то что мы нарисуем сейчас красным цветом – это и есть глобальная область видимости. А локальная область видимости это вот эта вот часть, только то, что находится в функции. Глобальная область видимости, если мы создаем переменную, вот здесь, global, если мы ниже создадим еще один тег скрипт, то тег скрипт будет представлять глобальную область видимости и в новом скрипте переменная global тоже будет существовать. То есть глобальные переменные – они общие. Они доступны даже тем JavaScript сценариям, которые разрабатывали другие разработчики. Если этот JavaScript сценарий подключить к вашей странице, то чужой JavaScript код может видеть ваши глобальные переменные. То есть почему они глобальные? Потому что доступны абсолютно всем. А вот локальные переменные, они доступны только в той области, в которой мы их создавали. Область видимости создается вот фигурными скобками, операторными скобками, все, что между этими скобками находится – это одна область видимости. Эта область видимости можем использовать свои локальные переменные и так же эта область видимости так как она находится внутри глобальной области видимости, эта область видимости может получать доступ также и к глобальным переменным. Вот сейчас мы на 20 строке увидим, что переменная global у нас доступна в функции F. Мы увидим здесь текс глобальная переменная. И также мы увидим, что доступна локальная переменная, на 21 строке мы увидим текст локальная переменная. Функцию мы определили, на строке 26 функцию мы эту запускаем, то она начинает у нас работать, создает локальную переменную, выводит все эти сообщения. После запрещения функции мы отображаем значение глобальной переменной. Мы увидим, что и здесь переменная была доступна и здесь переменная доступна у нас, видно текст глобальная переменная, но вот на 31 строчке отображение к локальной переменной уже ни к чему не приводит. Локальная переменная здесь неопределенна, локальна переменная доступна только в этой области видимости, то есть вот этой локальной области видимости. Поэтому на строке №31 мы не имеем права обращаться к переменной local, ее просто здесь не существует. Если бы переменную local отсюда подняли выше и поставили ее после глобальной, тогда переменная local, она по сути имела глобальную область видимости. А в этом случае, вот в таком варианте использования как на 31 строке, это у нас обращение по сути к несуществующей переменной. Давайте запустим и проверим. Глобальная и локальная переменная. То, что выводится функцией, видите, цвет navy, вот у нас отобразились данные, а потом, когда мы попытались вывести значения global и local за функции, то первая строчка отобразилась, а вторая строчка стала причиною ошибки, потому что переменная неопределенна. Переменную мы не смогли запустить.

В следующем примере показано, что произойдет если в глобальной области видимости находится переменная, которая называется точно так же, как и переменная заложена в локальную область видимости. На 15 строке создается глобальная переменная с именем global, на строке 18 еще одна переменная с именем local, но эта переменная глобальная, потому что вы видите, она определена в теге <script>, не внутри функции, а вот в скрипте. В самой функции F на строке 23 создается еще одна переменная с именем local. Имена этих переменных совпадают, но пока будет работать функция F, значение этой переменной временно будет перекрывать значение глобальной переменной, поэтому на строке 26, когда мы обращаемся к переменной с именем local, мы обращаемся к данной переменной, а когда мы закончим работу в этой функций, когда мы обратимся к переменной local на 34 строчке, то по сути мы будем работать с переменной, которая находится на 18 строке, будем выводить это значение. Получается, что внутри функции мы можем перекрывать значение глобальных переменных, если создадим локальную переменную с тем же именем, что и у глобальной. Вот видите глобальна переменная локальная переменная, а потом после выхода за функцию вот у нас есть глобальная и еще одна глобальная переменная. То есть на строке 34 мы на самом деле обращаемся к значению, которое устанавливалось на строке 18. А на строке 26 мы обращаемся к значению, которое устанавливалось на 23 строке. Вот это такая вот особенность работы с локальными и глобальными функциями.

В №4 примере показана ошибка, которую очень часто могут допускать разработчики. Всегда создавая переменную используйте ключевое слово var. Потому что если вы не установите ключевое слово var, это может привесит к следующему поведению. Вот на строке 19 мы определяем функцию. На 22 строчки создаем переменную с именем global, но не используя ключевое слово var. Если мы не устанавливаем ключевое слово var, это означает, что мы создаем глобальную переменную. Получается, что здесь эта переменная доступна как внутри этой функции, так как она глобальная, так и за пределами этой функции. На строке 35 мы имеем доступ к переменной global. Вот когда эта функция запускается – переменная global создается, а потом, когда на 35 строке мы к этой переменной обращаемся, мы видим ее результат – глобальная переменная, точнее ее значение. Видите, глобальная переменная вот вывелась на строке 24 и вторая строчка это вывелось на строке 35. Давайте проверим что произойдет если мы раскомментируем 30 строку, раскомментируем и запустим. Видите, браузер не отобразил никаких сообщений. Почему так произошло? Функцию на 19 строке мы определили, но код, который там находится еще не срабатывал. Далее на 30 строке мы обращаемся document.write, выводим в тело документа сообщение, и это значение, которое мы собираемся отобразить, это переменная global. Переменная global не была определена, переменная global создается только тогда, когда выполняется код заложенный в функции. А вы помните, чтобы код, находящийся в функции, запустился, выполнился, функцию необходимо запустить. Запускаем мы функцию на 32 строчке, то есть вот здесь, после того, как выведем значение глобальная переменная. Поэтому на строке 30 из-за отсутствия переменной у нас происходит ошибка, интерпретатор не в состоянии выполнять код, соответственно не выполняются ни это, ни следующие строчки кода, которые здесь заложены. Поэтому здесь у нас все прекращает работу. Если мы функцию перенесем на строку, например, 17, если мы ее вызовем раньше, то в таком случае у нас создается переменная, мы сможем с этой переменной работать на 30 и на 35 строке. Либо другой вариант, мы можем просто розкоментировать 16 строку и создать глобальную переменную на 16 строчке, вывести ее на 30 строке, а потом, когда мы запустим функцию, на строке 22 мы просто переопределим значение глобальной переменной, перепишем это значение, которое было создано здесь. Из-за того, что код, который мы создаем з глобальными переменными внутри функции получается такой вот неоднозначный и запутанный. Старайтесь всегда создавая переменную функции использовать ключевое слово var, потому что таким образом вы четко говорите, что данная переменная – локальная, она работает только в этой функции. Соответственно никаких проблем с понимаем к какой переменной вы сейчас обратитесь к локальной или глобальной никаких проблем не возникает. Возьмите себе за правило. Создавая переменную всегда используйте ключевое слово var.

Теперь следующий пример. Локальных областей видимости у нас может быть несколько, то есть каждая функция выступает своей собственной локальной областью видимости и из одной локальной области переменная не может быть получена в другой локальной области. Вот в примере, который мы сейчас открыли, у нас есть две функции, функция «а» и функция «b», эти функции мы на 38-39 строке вызываем. В функции «а» у нас создается локальная переменная с именем «а» и ее значение отображается на экран. В функции «b» локальная переменная «b», и тоже значение выводится на экран. Сейчас у нас есть по сути три области видимости в этом примере. Есть первая область видимости – глобальная, вот эта вот область видимости, вторая область видимости – это вот эта локальная функция «а» и область видимости локальная для функции «b». Между этими областями видимости мы не можем иметь никакого взаимодействия, то есть если расскоментировать эту строчку, в переменную «b» в локальной области видимости функции «а» мы не получим. То есть здесь мы можем работать только с теми переменными, которые были созданы в области видимости функции «а». И тоже самое касается с областью видимости функции «b», тут мы можем работать только с теми переменными, которые были созданы здесь, в этой области видимости. Ну а глобальная область видимости она и на то глобальная, то есть то что было создано в глобальной области видимости будет доступно и для функции «b» и для функции «а». Если мы так код запустим – он срабатывает, обе переменные выводится и если мы рассоментируем обращение к переменной «b», которое не существует в переменной «а», в функции «а», у нас выведется вот 21 строчка код сработает, а потом выводится исключение для интерпретатора и на 25 строке интерпретатор перестанет работать и перестанет выполнять все инструкции, которые заложены в нашем коде.

Следующий пример еще объясняет дополнительно работу области видимости. Я думаю, что многие из вас, кто работал с каким-нибудь другим языком программирования, увидев этот пример подумает, что язык JavaScript очень странный и непонятный язык. Мы работая с другими языками программирования привыкли, что если мы создаем условия, если мы создаем цикл, какую-то конструкцию, если в этом допустим цикле мы создаем переменную, то переменная доступна только для цикла. За пределами цикла переменная использоваться уже не будет. Но из-за того, то в JavaScript есть только две области видимости – глобальная и локальная – область видимости при создании цикла она у нас не появляется просто, вот из-за этого она у нас есть такие достаточно интересные поведение в коде. Когда на 14 строке создается цикл, мы указываем, что в цикле есть счетчик, изначальное значение его ноль, цикл будет работать до тех пор, пока счетчик меньше трех и на каждой итерации будет просто увеличивать счетчик. Вот все, что делает цикл. В большинстве языков программирования счетчик, который мы создали в цикле будет доступен только вот в этой части кода. То есть вот в той области видимости, в которой была создана вот данными фигурными скобками. На самом деле, когда на 14 строке мы создаем вот эту переменную, мы создаем ее по сути, как глобальную переменную, потому что мы не находимся сейчас в функции. Переменная counter становится глобальной, она доступна как к этому циклу, так и всем другим сценариям, которые есть в этом приложении. Поэтому на 19 строке, когда мы еще раз выводим сообщение, обращаемся к counter, у нас все корректно срабатывает. Переменная counter доступна даже после того, как цикл завершил свою работу. Видите, 0,1,2 это то, что выводилось в счетчике, в цикле точнее, а настройке 19 вот желтым цветом вывелся блок с счетчиком, и мы видим счетчик у нас продолжает работать, мы имеем доступ. Для тех, кто перешел в JavaScript с другого языка программирования, это достаточно такое странное поведение. Но я думаю, что вы привыкнете к такому использованию областей видимости.

И №7 пример также показывает особенности работы областей видимости в функциях, локальных областей видимости. По сути, код, который мы сейчас видим, это по сути тот же код, который и в №6 примере, но мы создали функцию в которую поместили цикл и вывод значение счетчика. И сейчас мы видим, что на строке 16 мы создали счетчик, мы так же проверили счетчик, использовали его для создания цикла, но когда мы закончили цикл, на 21 строке счетчик все-равно для нас доступен. Если в предыдущем примере мы создали счетчик как глобальную переменную, то сейчас мы создали счетчик как локальную переменную и получается, что все так же виден вот в этой области видимости. Если мы его создали где-нибудь в функции, он доступен во все этой функции. В других языках программирования вот эти фигурные скобочки, они бы тоже считались бы областью видимости, у нас бы еще появилась область видимости для цикла for, но это не пройдет в JavaScript коде, в JavaScript коде область видимости только одна, область видимости функции, поэтому создав переменную, эта переменная будет доступна в любой части функции. И кстати очень такой важный пример, который мы не показываем в этих примерах, но вы можете столкнутся с таким кодом, вы можете написать его и не понимать, почему у вас код работает неправильно. Если вы создаете переменную, вот, например, var «а» вот здесь, в конце, допустим создаете и устанавливаете для этой переменной значение 1. Переменную «а» вы можете начать использовать в самом начале. alert «а». Она будет работать, потому что интерпретатор в JavaScript коде работает по следующему принципу. Когда интерпретатор видит, что у вас есть функция, первое, что делает интерпретатор – это проходится по всей функции, собирает все переменные, которые тут находятся. То есть еще до того, как запустится код в функции, интерпретатор будет знать, что у вас есть переменная counter и переменная «а». Когда интерпретатор соберет информацию об этих переменных, он изначально создаст их, переменные будут со значением undefined. И вот зная о переменных, интерпретатор начнет выполнять уже код. На строке 15 первое, что сделает этот интерпретатор выполнит alert и выведет значение переменной «а». Но так как переменная «а» у нас будет инициализирована только на 22 строке, а интерпретатор уже знает о ее существовании, то сейчас на 15 строке мы увидим значение undefined. А вот здесь, после инициализации, мы уже увидим значение 1. Видите undefined, выполнится цикл и значение 1. Поэтому будьте осторожны при объявлении переменных. Очень часто JavaScript разработчики всегда эти переменные определять вначале функции. Вот есть даже такой шаблон. Вы пишете ключевое слово var, потом определяете первую переменную, потом вторую переменную, допустим, третью переменную, определяете все переменные вначале, а потом уже используете их в оставшееся части функции. Вот можете для себя пользоваться таким же правилом. Объявили переменные, возможно присвоили этим переменным стартовые значения, а потом уже начинайте этими переменными пользоваться. Это будет хороший способ обезопасить себя от этих ошибок, которые мы увидели с вами при определении переменной ближе к концу функции. На этом мы завершаем вторую часть урока, которая посвящена областям видимости.

Третья часть урока, которая посвящена областям видимости. Тереть часть урока – дополнительные варианты использование функции. Начнем мы с вами третью часть с такого понятия как рекурсия или рекурсивный вызов. Рекурсия или самовызов – это запуск функции из самой же функции. Вот на 13 строке определяется функция с именем F. Эта функция принимает параметр counter. На 15 строке counter уменьшается на 1, потом выводится сообщение какое-то значение counter вот сейчас в этой функции. На строке 19 делается проверка. Если counter не равен нулю, то в таком случае мы еще раз запуская функцию F, передавая counter, который был уменьшен. То есть со строки 20 мы прыгаем на строку 13 и повторно запускаем этот же код, уменьшаем counter, выводим сообщение и делаем проверку – если counter еще не равен нулю, повторно запускаем сами себя. То есть вот это и есть сама рекурсия, рекурсивный вызов, когда функция запускает сама себя. Первая часть запуска функции будет выводить вот это сообщение, но когда у нас на 19 строке условие у нас не сработает, и мы не попадем в строку 20, не сможем запустить сами себя, то тогда все функции, которые мы запускали, они начнут выводить вот это сообщение, значение счетчика, которое было после запуска. Сейчас мы попробуем это графически отобразить, чтобы вы понимали, как это все работает. Видите, вначале срабатывала первая часть функции, вот 17 строчка – счетчик был 2, 1, 0. А потом вторая часть функции, на 22 строчке вывела счетчика 0, 1, 2. Более подробно рекурсивный вызов рассматривается на курсе C# Starter, так как это основы программирования, но мы сейчас с вами тоже потратим немножко времени для того, чтобы понять как именно работает этот код, почему у нас так странно отображаются сообщения, почему вначале выводится 2, 1, 0, потом 0, 1, 2. Давайте сейчас представим, что функция, которую мы сейчас запускаем вот это наша функция, вот то что сейчас нарисую это есть функция F. В первой части функции мы счетчик уменьшаем, ведь изначально, когда на 25 строке мы запускаем функцию, мы передаем в счетчик значение три. Вот получается, что если сюда в counter попало значение три, то на 15 строчке счетчик уменьшается до двойки и это значение мы отображаем. На строчке 19, вот в этой вот функции мы делаем проверку counter не равен нулю. Так как counter сейчас равен 2, то на 20 строке мы запускаем сами себя же. То есть вот здесь у нас идет условие и в этом условии мы запускаем свою же функцию. Запускаем еще одну функцию, передаем в эту функцию счетчик, который вот здесь равен 2, в этой функции счетчик уже будет равен 1, потому что на 15 строке мы его уменьшим, счетчик равен 1, мы этот счетчик выводим, а потом на 19 строке делаем проверку, счетчик не ноль – да, о не ноль, вот здесь значение 1, поэтому вот здесь в условии, в этой копии функции мы условие проходим, попадаем в само условие и еще раз вызываем сами себя, вызываем еще такую же функцию, передавая в эту функцию счетчик со значение 1. То есть в этой локальной области видимости у функции в счетчике будет значение ноль, потому что на строке 15 эта функция счетчик уменьшает. Раз у этой функции значение ноль, мы это значение выведем, на 19 строке условие уже не срабатывает, то есть мы не можем вызвать функцию еще раз, потому что счетчик равен нулю, мы это условие проходим и на 20 строке выводим сообщение уже другим цветом, желтым цветом выводим значение счетчика ноль. Но как вы видели в предыдущих примерах, когда мы функцию запустили, мы перешли в тело функции, выполнили тело функции и вернулись обратно в ту часть кода, где мы функцию запускали. Вот вы видите, мы функцию выполнили, теперь нам нужно перейти обратно и завершить те предыдущие функции которые у нас остались. Как только вот здесь мы до конца выполняем вот эту функцию, мы возвращаемся вот туда, в точку вызова, где в предыдущую функцию и выводим желтым цветом значение счетчика, которое было в этой функции, то есть вот 22 строку, которая выполняется для этой копии функции. Выводим значение 1, уже желтым цветом. Когда эта функции до конца выполняется, она возвращается в ту часть кода, где была вызвана, возвращается в эту функцию, и эта функция соответственно тоже выводит желтым цветом значение 2, значение своего счетчика. Вот и получается, что у нас при рекурсивном вызове в нас вначале вывелось 2, потом 1, потом 0. Потом эта функция не сможет рекурсивно запустить саму себя еще раз, поэтому вывела нолик, который во второй части функции, вернулась в предыдущую функцию – вывела 1, в предыдущей функции вывела 2. Поэтому у нас получилось 2, 1, 0 и 0, 1, 2. Вот так работает рекурсивный вызов. Для чего может использоваться рекурсивный вызов?

В примере №2 мы посмотрим, как можно с помощью саморекурсии посчитать факториал. На строке 7 мы попросим пользователя ввести значение, на строке 8 конвертируем это значение в целочисленное, потом у нас идет функция, сейчас мы разберем, что она делает, а на 18 строке мы выводим факториал вот этого числа, знак восклицания, вы знаете как записывается факториал цифрой и знак восклицания, а потом результат, который возвращает функция, факториал с параметром input. То есть факториал – это произведение нескольких чисел. Если мы скажем 4! – это означает, что нам нужно 1*2*3*4 и вот этот результат, результат умножения, это и будет факториалом. Как мы можем делать вот так, чтобы сюда мы передаем значение 4, а в результат получаем 1*2*3*4, то есть произведение вот всех этих натуральных чисел. Мы можем создать функцию факториал, которая будет саморекурсивно работать. Когда мы запускаем функцию, первое что она проверяет – значение переменной «х», то есть параметра, который сюда попал. Если параметр меньше или равен 1, то мы возвращаем 1. Но если это условие не срабатывает, на строке 15 мы берем «х» и умножаем «х» на вызов этой же функции, факториал, но с уменьшенным значением «х» *1. Поэтому, когда мы сюда передаем значение 4, мы умножаем 4 на результат умножения 4-1, в свою очередь, в той функции, которую мы запустим будет результат умножения 3-1, а во вложенной функции 2-1. То есть в итоге у нас получится, что из-за этого рекурсивного вызова мы получим каждый раз факториал возвращает 1, потом результат умножения 2 на 1, потом результат умножения 3*1 и результат умножения 4 на все предыдущие результаты. И в итоге вот это рекурсивный вызов даст нам факториал указанного числа. Давайте попробуем. Введем число 4, Факториал 4 – 24. Введем значение 20, факториал числа будет вот таким большим значением.

Следующая интересная возможность, которая встречается в JavaScript – это создания функций-литералов. Так как функция – это тип данных, мы можем спокойно создать переменную и в эту переменную присвоить функцию. На 13 строке создается переменная с именем F. И в эту переменную, вместо обычного значение, которое мы привыкли присваивать, записывается функция. Вот это и есть функция-литерал, потому что мы создали по сути значение, которым инициализируем переменную. Тип переменной F – function. Если вы вспомните самый первый наш урок, на слайде мы разбирали, что один из типов данных – это функция. Вот мы видим, что действительно можем создать переменную типа функция. Вот мы сейчас это видим на 13 строке. Функция, которую мы присваиваем переменной, мы не устанавливаем этой функции имя, то есть по сути эта функция – это анонимная функция безымянная функция, мы ее не можем запустить просто так, само по себе, потому что мы не знаем, как она называется. Но когда эта функция находится в переменной F. По сути имя функции это есть имя переменной и на строке 17, когда мы обращаемся к переменной F, так как в переменной находится функция, чтобы эту функцию запустить, нам нужно установить круглые скобки после переменной. Вот у нас по сути запуск функции, не важно, как она была определена, либо как отдельное объявление функции, либо функциональный литерал, запуск функции всегда выглядит одинаково. Вот мы запустили функцию, которая выведет сообщение Hello. На строке 20 мы переменную “f” взяли и переприсвоили ей значение, записали в переменную новую функцию. Старую функцию, которая хранится в переменной мы выбросили, а новая функция теперь выводит сообщение bye. Проверяем на 24 строке, вызываем функцию, видим, что здесь уже выводится сообщение bye. Вот Hello, а второй раз bye. В этом примере мы увидели, как использовать функции bye. В следующих курсах мы будем очень часто принимать эти функции, когда мы создаем обработчики на события, мы тоже будем изучать такую тему, очень часто мы используем функции литералы. Так же в JavaScript коде можно сделать, чтобы функция в себе содержала вложенную функцию. Это не является ошибкой, это даже приветствуется. На строке 13 определяется функция с именем author. На строке 15 мы вызываем функцию inner. Мы видим, что функция inner она у нас определена вот, на строке 17. Вот фигурные скобки, которыми определяется тело функции author, мы видим, что между этими скобками находится еще одно определение вложенной функции с именем inner. Эта функция выводит параграф с текста – inner-function. И вот мы эту inner function вызвали на 15 строке и ее же вызвали на 21 строке, но для того, чтобы этот код заработал, на строке 24 мы запускаем функцию author. Когда мы вызываем на 24 строке author функцию, у нас начинает выполняться 15 строка и 21 строка. И по сути функция inner, она доступна только для локального кода, для функции author. За пределами функции author к этой функции мы обращаться не будем. Поэтому это один из принципов инкапсуляции. Если вы сталкивались с этим термином, то в JavaScript мы можем прятать какую-то функциональность с помощью вот таких вложенных функций.

В следующем примере показан пример использования таких вложенных функций. На строке №13 у нас есть функция, которая будет считать гипотенузу. В эту функцию мы передаем два параметра «а» и «b», и на 15 строке внутри функции гипотенуза у нас есть функция pow, то есть power, возведение в квадрат. Эта функция принимает один параметр и возвращает как бы этот параметр умноженный сам на себя. Просто «х» в квадрате. На строке 19 мы используем библиотеку Math, точнее не библиотеку, а объект Math. С этим объектом мы еще не сталкивались, но рекомендую его посмотреть, посмотреть его содержимое, его методы. Видите, что в этом объекте есть различные функции для получение синуса, косинуса, различных констант, например, число пи и так далее. Вот мы сейчас на объекте масс вызываем функцию sqrt. sqrt– это квадратный корень. Вот мы вызываем квадратный корень, а в квадратный корень, точнее в параметр, передаем значение квадрат «a», и квадрат «b». Вот мы вызываем локально эту функцию, которая доступна только нам, получаем квадрат «a», квадрат «b», и возвращаем результат в виде квадратного корня. Этот квадратный корень мы выводим на 22 строке. Вот мы вывели результат.

И в последнем примере мы разберем то как можно еще использовать функции. Так как функция – это тип данных, то мы без проблем можем передать функцию в качестве аргумента другой функции. Посмотрите сейчас на текущий пример. На строке 14 создается переменная с именем add и в эту переменную записывается функция, принимающая два параметра. На 18 строке – переменная sub, функция, которая тоже принимает два параметра, но эта функция у нас выполняет вычитание, а на 15 строчке – сложение. На 24 строке у нас есть функция show, которая принимает три аргумента. Первый аргумент имеет имя CallBack Function. Если вы встречаете такую фразу как CallBack, вот вообще термин CallBack, это функция обратного вызова. Это означает, что сюда, в функцию show, когда мы ее вызываем мы в эту функцию в качестве параметра должны передать еще одну функцию, CallBack функцию. И эта CallBack функция в какой-то определенный момент времени будет запущена. То есть функция show сама решит, когда нужно запустить CallBack Function. Запустит этот CallBack Function и по сути на оповестит. Можете воспринимать CallBack Function как допустим вы запускаете функцию show, и передаете функции show свой номер телефона, чтобы функция show, когда она что-то там надумает, смогла позвонить и оповестить о каких-то изменениях. CallBack Function – это функция, которую нужно запустить для того, чтобы оповестить наш код о каких-то изменениях. Можете воспринимать для простоты вот так эту функцию. Значит мы запускаем show, передаем три параметра – CallBack, «а» и «b». И смотрите, что мы делаем на 26 строке. Мы подразумеваем, что CallBack Function она умеет выполоть какие-то действия над двумя переменными. Выполнять какие-то действия, получить результат и потом с этим результатом что-то делать. Вводить его каким-то красивым способом на страницу, отправлять куда-то на сервер, то есть делать какие-то с ним операции. Вот получается, что на 26 строке мы запускаем CallBack Function, которая первым параметром была передана, в эту функцию передаем значение, которые были получены здесь в качестве аргументов «a» и «b». То, что функция возвращает мы записываем в переменную result, а потом что-то с этой переменной делаем. Выводим в документ или еще какие-то операции делаем. То есть задача функции show, получать на вход функцию, которая будет производить вычисления, получать на вход параметры, над какими нужно производить вычисления, запустить функцию, передать в эту функцию параметры, получить результат и что-то сделать с результатом. И вот строка 32, мы просим функция show, покажи то что произойдет, если взять функцию add, взять значение 10 и 20. То есть функции show мы сейчас передаем add метод, вот эту функцию, бросаем функцию show, бросаем в функцию show значение 10, значение 20. И в результате видим, что у нас сумма двух этих значений отображается в параметре. Строка 34, просим, чтобы функция show взяла функцию sub, провела вычитания, 50-30 и результат вывела в документ. Вот таким способом мы отображаем результат. Суть этого примера показать, что вы можете спокойно делать функции аргументами других функций. Вы можете функцию передать в качестве аргумента. А другая функция может воспользоваться переданной функцией, что-то с ней здесь, вызвать ее, посмотреть на результат, выполнить любые операции. В будущем вы еще не раз столкнетесь с CallBack функциями, особенно если будете работать с различными библиотеками, например, с JQuery библиотекой, moottolls, вы будете постоянно передавать CallBack функции в чужой код, чтобы чужой код вам выдавал оповещение, чтобы чужой код вызывая вашу CallBack функцию оповещал вас о каких-то изменениях. Но с этим мы еще с вами поговорим отдельно.

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

Подробнее о функциях | Карманная книга по TypeScript

  • Тип функции в форме выражения (function type expressions)
  • Сигнатуры вызова (call signatures)
  • Сигнатуры конструктора (construct signatures)
  • Общие функции или функции-дженерики
    • Предположение типа
    • Ограничения
    • Работа с ограниченными значениями
    • Определение параметров типа
  • Руководство по написанию хороших функций-дженериков
    • Используйте типы параметра без ограничений
    • Используйте минимальное количество типов параметра
    • Типы параметра должны указываться дважды
  • Опциональные параметры
    • Опциональные параметры в функциях обратного вызова
  • Перегрузка функции (function overload)
    • Сигнатуры перегрузки и сигнатура реализации (overload signatures and the implementation signature)
    • Правила реализации хороших перегрузок функции
  • Определение this в функциях
  • Другие типы, о которых следует знать
    • void
    • object
    • unknown
    • never
    • Function
  • Оставшиеся параметры и аргументы
    • Оставшиеся параметры (rest parameters)
    • Оставшиеся аргументы (rest arguments)
  • Деструктуризация параметров (parameter destructuring)
    • Возможность присвоения функций переменным

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

Тип функции в форме выражения (function type expressions)#

Простейшим способом описания типа функции является выражение. Такие типы похожи на стрелочные функции:

Выражение (a: string) => void означает «функция с одним параметром a типа string, которая ничего не возвращает». Как и в случае с определением функции, если тип параметра не указан, он будет иметь значение any.

Обратите внимание

Название параметра является обязательным. Тип функции (string) => void означает «функция с параметром string типа any«!

Разумеется, для типа функции можно использовать синоним:

Сигнатуры вызова (call signatures)#

В JS функции, кроме того, что являются вызываемыми (callable), могут иметь свойства. Однако, тип-выражение не позволяет определять свойства функции. Для описания вызываемой сущности (entity), обладающей некоторыми свойствами, можно использовать сигнатуру вызова (call signature) в объектном типе:

Обратите внимание

Данный синтаксис немного отличается от типа-выражения функции — между параметрами и возвращаемым значением используется : вместо =>.

Сигнатуры конструктора (construct signatures)#

Как известно, функции могут вызываться с ключевым словом new. TS считает такие функции конструкторами, поскольку они, как правило, используются для создания объектов. Для определения типов таких функций используется сигнатура конструктора:

Некоторые объекты, такие, например, как объект Date, могут вызываться как с, так и без new. Сигнатуры вызова и конструктора можно использовать совместно:

Общие функции или функции-дженерики#

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

Функция делают свою работу, но, к сожалению, типом возвращаемого значения является any. Было бы лучше, если бы функция возвращала тип элемента массива.

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

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

Предположение типа#

Мы можем использовать несколько параметров типа. Например, самописная версия функции map может выглядеть так:

Обратите внимание

В приведенном примере TS может сделать вывод относительно типа Input на основе переданного string[], а относительно типа Output на основе возвращаемого number.

Ограничения#

Ограничение, как следует из названия, используется для ограничения типов, принимаемых параметром типа.

Реализуем функцию, возвращающую самое длинное из двух значений. Для этого нам потребуется свойство length, которое будет числом. Мы ограничим параметр типа типом number с помощью ключевого слова extends:

Мы позволяем TS предполагать тип значения, возвращаемого из функции longest.

Поскольку мы свели Type к { length: number }, то получили доступ к свойству length параметров a и b. Без ограничения типа у нас бы не было такого доступа, потому что значения этих свойств могли бы иметь другой тип — без длины.

Типы longerArr и longerStr были выведены на основе аргументов. Запомните, дженерики определяют связь между двумя и более значениями одного типа!

Наконец, как мы и ожидали, вызов longest(10, 100) отклоняется, поскольку тип number не имеет свойства length.

Работа с ограниченными значениями#

Вот пример распространенной ошибки, возникающей при работе с ограничениями дженериков:

На первый взгляд может показаться, что все в порядке — Type сведен к { length: number }, и функция возвращает либо Type, либо значение, совпадающее с ограничением. Проблема состоит в том, что функция может вернуть объект, идентичный тому, который ей передается, а не просто объект, совпадающий с ограничением. Если бы во время компиляции не возникло ошибки, мы могли бы написать что-то вроде этого:

Определение параметров типа#

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

При обычном вызове данной функции с несовпадающими по типу массивами возникает ошибка:

Однако, мы можем вручную определить Type, и тогда все будет в порядке:

Руководство по написанию хороших функций-дженериков#

Используйте типы параметра без ограничений#

Рассмотрим две похожие функции:

Предполагаемым типом значения, возвращаемого функцией firstElement1 является Type, а значения, возвращаемого функцией firstElement2any. Это объясняется тем, что TS разрешает (resolve) выражение arr[0] с помощью ограничения типа вместо того, чтобы ждать разрешения элемента после вызова функции.

Правило: по-возможности, используйте параметры типа без ограничений.

Используйте минимальное количество типов параметра#

Вот еще одна парочка похожих функций:

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

Правило: всегда используйте минимальное количество параметров типа.

Типы параметра должны указываться дважды#

Иногда мы забываем, что функция не обязательно должна быть дженериком:

Вот упрощенная версия данной функции:

Запомните, параметры типа предназначены для связывания типов нескольких значений.

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

Опциональные параметры#

Функции в JS могут принимать произвольное количество аргументов. Например, метод toFixed принимает опциональное количество цифр после запятой:

Мы можем смоделировать это в TS, пометив параметр как опциональный с помощью ?:

Несмотря на то, что тип параметра определен как number, параметр x на самом деле имеет тип number | undefined, поскольку неопределенные параметры в JS получают значение undefined.

Мы также можем указать «дефолтный» параметр (параметр по умолчанию):

Теперь в теле функции f параметр x будет иметь тип number, поскольку любой аргумент со значением undefined будет заменен на 10. Обратите внимание: явная передача undefined означает «отсутствующий» аргумент.

Опциональные параметры в функциях обратного вызова#

При написании функций, вызывающих «колбеки», легко допустить такую ошибку:

Указав index?, мы хотим, чтобы оба этих вызова были легальными:

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

Поэтому попытка вызова такой функции приводит к ошибке:

В JS при вызове функции с большим (ударение на первый слог) количеством аргументов, чем указано в определении фукнции, дополнительные параметры просто игнорируются. TS ведет себя аналогичным образом. Функции с меньшим количеством параметров (одного типа) могут заменять функции с большим количеством параметров.

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

Перегрузка функции (function overload)#

Некоторые функции могут вызываться с разным количеством аргументов. Например, мы можем написать функцию, возвращающую Date, которая принимает время в мс (timestamp, один аргумент) или день/месяц/год (три аргумента).

В TS такую функцию можно реализовать с помощью сигнатур перегрузки (overload signatures). Для этого перед телом функции указывается несколько ее сигнатур:

В приведенном примере мы реализовали две перегрузки: одну, принимающую один аргумент, и вторую, принимающую три аргумента. Первые две сигнатуры называются сигнатурами перегрузки.

Затем мы реализовали функцию с совместимой сигнатурой (compatible signature). Функции имеют сигнатуру реализации (implementation signature), но эта сигнатура не может вызываться напрямую. Несмотря на то, что мы написали функцию с двумя опциональными параметрами после обязательного, она не может вызываться с двумя параметрами!

Сигнатуры перегрузки и сигнатура реализации (overload signatures and the implementation signature)#

Предположим, что у нас имеется такой код:

Почему в данном случае возникает ошибка? Дело в том, что сигнатура реализации не видна снаружи (за пределами тела функции). Поэтому при написании перегруженной функции всегда нужно указывать две или более сигнатуры перегрузки перед сигнатурой реализации.

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

Правила реализации хороших перегрузок функции#

Рассмотрим функцию, возвращающую длину строки или массива:

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

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

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

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

Определение

this в функциях#

Рассмотрим пример:

TS «понимает», что значением this функции user.becomeAdmin является внешний объект user. В большинстве случаев этого достаточно, но порой нам требуется больше контроля над тем, что представляет собой this. Спецификация JS определяет, что мы не можем использовать this в качестве названия параметра. TS использует это синтаксическое пространство (syntax space), позволяя определять тип this в теле функции:

Обратите внимание

В данном случае мы не можем использовать стрелочную функцию.

Другие типы, о которых следует знать#

void#

void представляет значение, возвращаемое функцией, которая ничего не возвращает. Если в теле функции отсутствует оператор return или после этого оператора не указано возвращаемого значения, предполагаемым типом возвращаемого такой функцией значения будет void:

В JS функция, которая ничего не возвращает, «неявно» возвращает undefined. Однако, в TS void и undefined — это разные вещи.

Обратите внимание

void — это не тоже самое, что undefined.

object#

Специальный тип object представляет значение, которое не является примитивом (string, number, boolean, symbol, null, undefined). object отличается от типа пустого объекта ({}), а также от глобального типа Object. Скорее всего, вам никогда не потребуется использовать Object.

Правило: object — это не Object. Всегда используйте object!

Обратите внимание

В JS функции — это объекты: они имеют свойства, Object.prototype в цепочке прототипов, являются instanceof Object, мы можем вызывать на них Object.keys и т.д. По этой причине в TS типом функций является object.

unknown#

Тип unknown представляет любое значение. Он похож на тип any, но является более безопасным, поскольку не позволяет ничего делать с неизвестным значением:

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

never#

Некоторые функции никогда не возвращают значений:

Тип never представляет значение, которого не существует. Чаще всего, это означает, что функция выбрасывает исключение или останавливает выполнение программы.

never также появляется, когда TS определяет, что в объединении больше ничего не осталось:

Function#

Глобальный тип Function описывает такие свойства как bind, call, apply и другие, характерные для функций в JS. Он также имеет специальное свойство, позволяющее вызывать значения типа Function — такие вызовы возвращают any:

Такой вызов функции называется нетипизированным и его лучше избегать из-за небезопасного возвращаемого типа any.

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

Оставшиеся параметры и аргументы#

Оставшиеся параметры (rest parameters)#

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

Оставшиеся параметры указываются после других параметров с помощью ...:

В TS неявным типом таких параметров является any[], а не any. Любая аннотация типа для них должна иметь вид Array<T> или T[], или являться кортежем.

Оставшиеся аргументы (rest arguments)#

Синтаксис распространения (синонимы: расширение, распаковка) (spread syntax) позволяет передавать произвольное количество элементов массива. Например, метод массива push принимает любое количество аргументов:

Обратите внимание

TS не считает массивы иммутабельными. Это может привести к неожиданному поведению.

Самым простым решением данной проблемы является использование const:

Деструктуризация параметров (parameter destructuring)#

Деструктуризация параметров используется для распаковки объекта, переданного в качестве аргумента, в одну или более локальную переменную в теле функции. В `JS` это выглядит так:

Аннотация типа для объекта указывается после деструктуризации:

Для краткости можно использовать именованный тип:

Возможность присвоения функций переменным#

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

Контекстуальная типизация (contextual typing), основанная на void, не запрещает функции что-либо возвращать. Другими словами, функция, типом возвращаемого значения которой является voidtype vf = () => void, может возвращать любое значение, но это значение будет игнорироваться.

Все приведенные ниже реализации типа () => void являются валидными:

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

Поэтому следующий код является валидным, несмотря на то, что Array.prototype.push возвращает число, а Array.prototype.forEach ожидает получить функцию с типом возвращаемого значения void:

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

14 функций JavaScript, которые вы должны понимать

Востребованность языка JavaScript с каждым годом неумолимо растет. В 2019  этот язык уступил Java всего о,14% доли на украинском рынке. В 2020 году число вакансий в категории  JavaScript значительно увеличится, поэтому можете уже редактировать ваше резюме. А прежде чем идти на собеседование, вы должны понимать и уметь написать эти 14 функции JavaScript.

1 Определение конкретного типа любого объекта

Как мы знаем, в JavaScript есть пять примитивных типов данных и объектный тип данных. Но знаете ли вы, что объектный тип данных можно подразделить на множество начальных типов? Объект может быть массивом, функцией, картой и т. д. Если мы хотим получить конкретный тип объекта, что нам делать?

Код:

Объяснение:

ECMAScript имеет следующие правила:

Для разных объектов разные результаты будут возвращены при вызове Object.prototype.toString ().

Кроме того, возвращаемое значение Object.prototype.toString () всегда имеет формат ‘[object’ + ‘tag’ + ‘]’. Если нам нужен только средний тег, мы можем удалить символы с обеих сторон с помощью регулярного выражения или String. prototype.slice ().

Пример: 

toRawType(null) 
// "Null"toRawType(/sdfsd/) 
//"RegExp"

 

2 Результаты расчета функции кэширования

Если есть такая функция:

function computed(str) {  
  // Suppose the calculation in the funtion is very time consuming    
console.log('2000s have passed')   
 return 'a result'
}

Мы хотим кешировать результат работы функции. Когда он вызывается позже, если параметры совпадают, функция больше не будет выполняться, но результат в кеше будет возвращен напрямую. Что мы можем сделать?

Код:

Пример: 

 

 

3 Реализация Array.prototype.map

Это полезный встроенный метод в JavaScript, но вы должны иметь возможность реализовать эту функцию самостоятельно.

Код:

Пример: 

 

4 Реализация Array.prototype.filter

Это полезный встроенный метод в JavaScript, но вы должны иметь возможность реализовать эту функцию самостоятельно.

Код:

Пример: 

 

 5 Реализация Array.prototype.some

Это полезный встроенный метод в JavaScript, но вы должны иметь возможность реализовать эту функцию самостоятельно.

Код:

Пример:

 

6 Реализация Array.prototype.reduce

Это полезный встроенный метод в JavaScript, но вы должны иметь возможность реализовать эту функцию самостоятельно.

Код:

Пример:

 

7 Реализация Array.prototype.flat.

Код:

Пример:

 

8 Каррирование

Карринг – это метод преобразование функции от многих аргументов в набор функций, каждая из которых является функцией от одного аргумента.

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

Именно тогда мы превращаем вызов функции add (1,2,3) в add (1) (2) (3). Используя эту технику, маленький кусочек можно легко настроить и использовать повторно.

Почему это полезно?

  • Карринг помогает избежать повторной передачи одной и той же переменной.
  • Это помогает создать функцию более высокого порядка. Это чрезвычайно полезно при обработке событий.
  • Маленькие куски можно легко настроить и использовать повторно.

Давайте посмотрим на простую функцию добавления. Он принимает три операнда в качестве аргументов и возвращает сумму всех трех в качестве результата.

function add(a,b,c){
 return a + b + c;
}

Вы можете вызывать несколько (с нечетными результатами) или много (лишние аргументы игнорируются).

add(1,2,3) --> 6 
add(1,2) --> NaN
add(1,2,3,4) --> 6 //Extra parameters will be ignored.

Как преобразовать существующую функцию в curry?

Код:

Пример:

 

9 Debouncing

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

Предположим, пользователь хочет получить «Учебный комплект Tutorix». Он вводит каждый символ продукта в строке поиска. После ввода каждого символа происходит вызов API от браузера к серверу, чтобы получить нужный продукт. Поскольку ему нужен «учебный комплект Tutorix», пользователь должен сделать 24 вызовов Api из браузера на сервер. Подумайте о сценарии, который, когда миллионы людей проводят одинаковый поиск, вызывает миллиарды Api. Поэтому одновременное обращение к миллиардам Api определенно приведет к снижению производительности браузера. Чтобы уменьшить этот недостаток, на сцену выходит Debouncing.

В этом случае Debouncing установит интервал времени, например, 2 секунды, между двумя нажатиями клавиш. Если время между двумя нажатиями клавиш превышает 2 секунды, происходит только вызов Api. В течение этих 2 секунд пользователь может набрать хотя бы несколько символов, уменьшая количество этих вызовов Api. Поскольку количество вызовов Api уменьшилось, производительность браузера будет увеличена. Необходимо учитывать, что функция Debouncing обновляется при каждом нажатии клавиши.

Код:

 

10 Throttling

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

Код:

 

11 “Ленивая загрузка” изображений

Ленивая загрузка изображений означает асинхронную загрузку изображений на веб-сайты, то есть после полной загрузки указанного выше содержимого или даже условно, только когда они появляются в окне просмотра браузера. Это означает, что если пользователи не будут полностью прокручивать страницу, изображения, размещенные внизу страницы, даже не будут загружены.

Код:

 

12 Перемешивание массива

Часто возникает необходимость перемешать массив.

Код:

Пример:

 

13 Singleton

Singleton паттерн ограничивает количество экземпляров определенного объекта только одним. Этот единственный экземпляр называется синглтоном.

Синглтоны полезны в ситуациях, когда общесистемные действия должны координироваться из единого центрального места. Примером является пул соединений с базой данных. Пул управляет созданием, уничтожением и временем жизни всех соединений с базой данных для всего приложения, гарантируя, что никакие соединения не будут «потеряны».

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

Код:

Пример:

 

14 Реализация JSON.stringify

Это полезный встроенный метод в JavaScript, но вы должны иметь возможность реализовать эту функцию самостоятельно.

Код:

Пример:

Оригинал статьи на medium.com

Функции — Kotlin

В Kotlin функции объявляются с помощью ключевого слова fun.

fun double(x: Int): Int {
    return 2 * x
}

Использование функций

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

val result = double(2)

Для вызова вложенной функции используется знак точки.

Stream().read() //создаёт экземпляр класса Stream и вызывает read()

Параметры

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

fun powerOf(number: Int, exponent: Int): Int { /*...*/ }

Вы можете использовать завершающую запятую при объявлении параметров функции.

fun powerOf(
    number: Int,
    exponent: Int, // завершающая запятая
) { /*...*/ }

Аргументы по умолчанию

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

fun read(
    b: ByteArray,
    off: Int = 0,
    len: Int = b.size,
) { /*...*/ }

Значения по умолчанию указываются после типа знаком =.

Переопределённые методы всегда используют те же самые значения по умолчанию, что и их базовые методы. При переопределении методов со значениями по умолчанию в сигнатуре эти параметры должны быть опущены.

open class A {
    open fun foo(i: Int = 10) { /*...*/ }
}
class B : A() {
    override fun foo(i: Int) { /*...*/ } // значение по умолчанию указать нельзя
}

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

fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }
foo(baz = 1) // Используется значение по умолчанию bar = 0

Но если последний аргумент после параметров по умолчанию — лямбда, вы можете передать её либо как именованный аргумент, либо за скобками.

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*...*/ }
foo(1) { println("hello") }     // Используется значение по умолчанию baz = 1 
foo(qux = { println("hello") }) // Используется оба значения по умолчанию: bar = 0 и baz = 1
foo { println("hello") }        // Используется оба значения по умолчанию: bar = 0 и baz = 1

Именованные аргументы

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

null значение.

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

Рассмотрим следующую функцию reformat(), которая имеет 4 аргумента со значениями по умолчанию:

fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*.
..*/ }

При её вызове, вам не нужно явно указывать все имена аргументов.

reformat(
    "String!",
    false,
    upperCaseFirstLetter = false,
    divideByCamelHumps = true,
    '_'
)

Вы можете пропустить все аргументы со значением по умолчанию.

reformat("This is a long String!")

Вы также можете пропустить не только все аргументы со значениями по умолчанию, но и лишь некоторые из них. Однако после первого пропущенного аргумента вы должны указывать имена всех последующих аргументов.

reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')

Вы можете передать переменное количество аргументов (

vararg) с именами, используя оператор spread.

fun foo(vararg strings: String) { /*...*/ }
foo(strings = *arrayOf("a", "b", "c"))

В JVM: синтаксис именованных аргументов не может быть использован при вызове Java функций, потому как байт-код Java не всегда сохраняет имена параметров функции.

Функции с возвращаемым типом Unit

Если функция не возвращает никакого полезного значения, её возвращаемый тип — Unit. Unit — тип только с одним значением — Unit. Это значение не нуждается в явном указании возвращения функции.

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // `return Unit` или `return` необязательны
}

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

fun printHello(name: String?) { /*...*/ }

Функции с одним выражением

Когда функция возвращает одно единственное выражение, фигурные скобки { } могут быть опущены, и тело функции может быть описано после знака =.

fun double(x: Int): Int = x * 2

Явное объявление возвращаемого типа является необязательным, когда он может быть определен компилятором.

fun double(x: Int) = x * 2

Явные типы возвращаемых значений

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

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

Нефиксированное число аргументов (varargs)

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

vararg.

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts - это массив (Array)
        result.add(t)
    return result
}

Это позволит указать несколько значений в качестве аргументов функции.

val list = asList(1, 2, 3)

Внутри функции параметр с меткой vararg и типом T виден как массив элементов T, таким образом переменная ts в вышеуказанном примере имеет тип Array<out T>.

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

При вызове vararg-функции вы можете передать аргументы один за другим, например asList(1, 2, 3), или, если у нас уже есть необходимый массив элементов и вы хотите передать его содержимое в функцию, используйте оператор spread (необходимо пометить массив знаком *).

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

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

val a = intArrayOf(1, 2, 3) // IntArray - массив примитивного типа
val list = asList(-1, 0, *a. toTypedArray(), 4)

Инфиксная запись

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

  • Они должны являться членом другой функции или расширения;
  • В них должен использоваться только один параметр;
  • Параметр не должен принимать переменное количество аргументов и не должен иметь значения по умолчанию.
infix fun Int.shl(x: Int): Int { /*...*/ }
// вызов функции, с использованием инфиксной записи
1 shl 2
// то же самое, что
1.shl(2)

Вызовы инфиксных функций имеют более низкий приоритет, чем арифметические операторы, приведение типов и оператор rangeTo. Следующие выражения эквивалентны:

  • 1 shl 2 + 3 эквивалентно 1 shl (2 + 3),
  • 0 until n * 2 эквивалентно 0 until (n * 2),
  • xs union ys as Set<*> эквивалентно xs union (ys as Set<*>).

С другой стороны, приоритет вызова инфиксной функции выше, чем у логических операторов && и ||, is— и in-проверок и некоторых других операторов. Эти выражения также эквивалентны:

  • a && b xor c эквивалентно a && (b xor c),
  • a xor b in c эквивалентно (a xor b) in c.

Обратите внимание, что инфиксные функции всегда требуют указания как получателя, так и параметра. Когда вы вызываете метод на текущем приемнике, используя инфиксную запись, явно используйте this. Это необходимо для обеспечения однозначного синтаксического анализа.

class MyStringCollection {
    infix fun add(s: String) { /*...*/ }
    fun build() {
        this add "abc" // Верно
        add("abc")     // Верно
        //add "abc"    // Не верно: получатель должен быть указан
    }
}

Область видимости функций

В Kotlin функции могут быть объявлены в самом начале файла, что значит, что вам необязательно создавать класс, чтобы воспользоваться его функцией (как в Java, C# или Scala). В дополнение к этому, функции в Kotlin могут быть объявлены локально, как функции-члены и функции-расширения.

Локальные функции

Kotlin поддерживает локальные функции, т.е. функции, вложенные в другие функции.

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }
    dfs(graph.vertices[0], HashSet())
}

Локальная функция может иметь доступ к локальным переменным внешних по отношению к ним функций (типа closure). Таким образом, в примере, приведённом выше, visited может быть локальной переменной.

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }
    dfs(graph.vertices[0])
}

Функции-члены

Функции-члены — это функции, объявленные внутри классов или объектов.

class Sample {
    fun foo() { print("Foo") }
}

Функции-члены вызываются с использованием точки.

Sample().foo() // создаёт инстанс класса Sample и вызывает его функцию foo

Для более подробной информации о классах и их элементах см. Классы и Наследование.

Функции-обобщения

Функции могут иметь обобщённые параметры, которые задаются треугольными скобками и помещаются перед именем функции.

fun <T> singletonList(item: T): List<T> { /*...*/ }

Для более подробной информации см. Обобщения.

Функции с хвостовой рекурсией

Kotlin поддерживает стиль функционального программирования, известный как «хвостовая рекурсия». Это позволяет использовать циклические алгоритмы вместо рекурсивных функции, но без риска переполнения стэка. Когда функция помечена модификатором tailrec и её форма отвечает требованиям компилятора, он оптимизирует рекурсию, оставляя вместо неё быстрое и эффективное решение этой задачи, основанное на циклах. -15 private fun findFixPoint(): Double { var x = 1.0 while (true) { val y = Math.cos(x) if (Math.abs(x — y) < eps) return x x = Math.cos(x) } }

Для соответствия требованиям модификатора tailrec, функция должна вызывать сама себя в качестве последней операции, которую она предпринимает. Вы не можете использовать хвостовую рекурсию, когда существует ещё какой-то код после вызова этой самой рекурсии. Также нельзя использовать её внутри блоков try/catch/finally или в open функциях. На данный момент хвостовая рекурсия поддерживается только в backend виртуальной машины Java (JVM) и в Kotlin/Native.

См. также:

  • Встроенные функции,
  • Функции-расширения,
  • Высокоуровневые функции и лямбды.

Я раньше никогда не понимал замыкания в JavaScript …

Spread the love

Что такое замыкание и как оно работает? Очередной достаточно частый вопрос на собеседованиях. Я всегда думал что я знаю что ответить на этот вопрос. И всегда отвечал что типа такого… Замыкание это когда функция низшего порядка получается доступе к переменным функции высшего порядка. Но знать приблизительное определение и реально разбираться в замыкание не совсем одно и тоже. Давайте рассмотрим простейший пример:

function createCounter() {
   let counter = 0
   const myFunction = function() {
     counter = counter + 1
     return counter
   }
   return myFunction
 }
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3)

Как по вашему что должно быть выведено в консоль? Если вы точно знаете ответ, то можете пропустить эту статью. А если думаете что 1,1,1 или если у вас возникло какое либо сомнение, рекомендую продолжить чтение далее….

Статья получилась большой, с длинными последовательностями описания маленьких примеров. Но если вас это не смутит и вы пройдетесь по этим описаниям, в награду получите более полное понимание того как работает замыкание.

До того как мы начнем

Перед тем как мы продолжим, хотел оговорить некоторые важные понятия, которые нам понадобятся дальше. Одним из них является контекст исполнения.

Это статья хорошо объясняет что такое Контекст исполнения (Execution Context). Основные понятия из статьи:

Когда код выполняется, среда, в которой он выполняется, очень важна и определяется как то так:

Глобальный код (Global code —  Среда по умолчанию, где код выполняется в первый раз.

Код функции (Function code) —  всякий раз, когда поток выполнения входит в тело функции.

(…), давайте думать о термине контекст исполнения как о среде/области видимости (scope), в которой выполняется текущий код.

Другими словами, когда запускается программа, ее код начинается исполнятся в глобальном контексте. Как правило некоторое количество переменных объявляются в глобальном контексте выполнения. Мы называем их глобальными переменными. А что происходит когда программа вызывает функцию? Вот список шагов:

  1. JavaScript создает новый контекст исполнения, который становится локальным для текущего потока
  2. Этот локальный контекст исполнения будет иметь свой собственный набор переменных, и они будут локальными для этого контекста.
  3. Новый контекст переносится в стек исполнения. Думайте о стеке как о механизме, позволяющем отслеживать, где в текущий момент исполняется программа.

Что происходит когда функция заканчивает выполнение? Выполнение заканчивается когда встречается оператор return или соответствующая закрывающая скобка }:

  1. Локальные контексты исполнения извлекается из стека выполнения
  2. Функции отправляют возвращаемое значение обратно в вызывающий контекст. Вызывающий контекст — это контекст который вызвал эту функцию. Им может быть глобальный контекст или другой локальный контекст выполнения. Возвращаемое значение может быть объектом, массивом, функцией, boolean или чем угодно. Если функция не имеет оператора возврата return, возвращается  undefined.
  3. Локальный контекст уничтожается. Все переменные, которые были объявлены в локальном контексте выполнения, удаляются и они больше не доступны. Вот почему они называются локальными переменными.

Очень простой пример

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

1: let a = 3
2: function addTwo(x) {
3:   let ret = x + 2
4:   return ret
5: }
6: let b = addTwo(a)
7: console. log(b)

Чтобы понять, как на самом деле работает движок JavaScript, давайте разберем этот пример подробно.

  1. В строке 1 объявляется новая переменная  a  в глобальном контексте выполнения и ей присваивается значение 3
  2. Далее становится чуть сложнее. В строках со 2 по 5 объявляется новая переменная с именем  addTwo  в глобальном контексте выполнения. И что ей назначается? Определение функции. Все, что находится между двумя скобками,  { } присваивается  addTwo. Код внутри функции на данный момент не выполняется, он просто сохраняется в переменной для будущего использования.
  3. Итак, теперь мы находимся на строке 6. Тут объявляется новая переменная  b в глобальном контексте. Как только переменная объявлена, она имеет значение undefined.
  4. Далее, все еще в строке 6, мы видим оператор присваивания. Далее идет вызов функции. Когда вы видите переменную, за которой следуют круглые скобки  (…), это сигнал о том, что это вызов функции. Забегая вперед, заметим что каждая функция возвращает что-либо (значение, объект или  undefined). Все, что возвращается из функции, будет присвоено переменной b.
  5. Но сначала нам нужно вызвать функцию с именем  addTwo. Что бы ее запустить JavaScript нужно найти переменную с именем  addTwo в глобальном контексте. Он ее найдет, так как мы ее объявили в строке 2 (или строки 2–5). Обратите внимание, что переменная  a  передается в качестве аргумента функции. JavaScript так же будет искать переменную a  в глобальном контекста, и после того как, найдет ее, передаст число 3 в качестве аргумента функции. Теперь все готово что бы выполнить функцию.
  6. Далее произойдет переключения контекста выполнения. Вначале создается новый локальный контекст. Затем он помещается в стек вызовов. Что в первую очередь должно быть выполнено в локальном контексте?
  7. У вас может возникнуть соблазн сказать: «В локально контексте будет объявлена новая переменная  ret. Это не совсем верный ответ. Правильный ответ: сначала объявляются параметры функции. То есть в нашем случае объявляется новая переменная  x  в локальном контексте. И поскольку значение 3 было передано в качестве аргумента, переменной  x  присваивается значение 3.
  8. Следующий шаг: новая переменная ret  объявляется в локальном контексте. Ее значение устанавливается как undefined. (строка 3)
  9. Все еще на строка 3, сначала нам нужно значение переменной  x. JavaScript начинает искать переменную  x. В начале она будет искаться в локальном контексте. И сразу же там будет найдена со значение 3. А вторым операндом будет число 2. Результат сложения (5) присваивается переменной ret.
  10. Строка 4. Возвращается содержимое переменной ret. Еще один поиск в локальном контексте. ret содержит значение 5. Функция возвращает число 5. И на этом шаге функция завершится.
  11. Строки 4–5. Функция завершится. Локальный контекст будет уничтожен. Переменные x и ret так же уничтожаются. Контекст извлекается из стека вызовов, а возвращаемое значение возвращается в вызывающий контекст. В этом случае вызывающий контекст будет глобальным контекстом выполнения, потому что функция  addTwo была вызвана из глобального контекста.
  12. Мы все еще на шестой строчке. Возвращаемое значение (число 5) присваивается переменной b.
  13. В строке 7 содержимое переменной b выводится на консоль. В нашем примере это число 5.

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

Лексический контекст.

Далее я бы хотел коснуться некоторых аспектов лексического контекста. Посмотрите на следующий пример.

1: let val1 = 2
2: function multiplyThis(n) {
3:   let ret = n * val1
4:   return ret
5: }
6: let multiplied = multiplyThis(6)
7: console. log('example of scope:', multiplied)

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

  1. Объявление новой переменной val1 в глобальном контексте и назначение ей значение 2.
  2. Строки 2–5. Объявление новой переменной multiplyThis и назначение ей определение функции.
  3. Строка 6. Объявление новой переменной multiplied в глобальном контексте.
  4. Получение переменной multiplyThis  из глобального контекста и выполнение функции. Передача значения  в качестве аргумента.
  5. Вызов функции = новый контекст . Создается новый локальный контекст.
  6. В локальном контексте объявляется переменная n и ей назначается значение 6.
  7. Строка 3. В локальном контексте объявляется переменная ret.
  8. Строка 3 (продолжение). Выполняется умножение с двумя операндами; переменными n и val1. Переменная n берется из локального контекста. Мы объявили ее в строке 6. Ее значение 6. Ищется переменная val1 в локальном контексте. В локальном контексте ее нет. Далее ищется в вызываемом контексте. В данном случае вызываемый контекст является глобальным. И там она находится. Она была определена в строке 1. Ее значение равно 2.
  9. Строка 3 (продолжение). Умножаются два операнда и результат назначается переменной ret. 6 * 2 = 12. Значение ret в данный момент 12.
  10. Далее из функции возвращается значение ret. Локальный контекст уничтожается вместе с переменными ret и n. Переменная val1 не уничтожается, так как она находится в глобальном контексте.
  11. Строка 6. В вызывающем контексте переменной  multiplied назначается значение 12.
  12. И наконец строка 7, значение переменной multiplied отображается в консоле.

Таким образом, в этом примере мы продемонстрировали, что функция имеет доступ к переменным, которые определены в контексте вызова. Формальное название этого явления — лексическая область видимости (lexical scope).

А что если функция возвращает функцию

В первом примере функция addTwo возвращает номер. Мы знаем что функция может вернуть что угодно. Давайте рассмотрим пример когда функция возвращает функцию.

1: let val = 7
2: function createAdder() {
3:   function addNumbers(a, b) {
4:     let ret = a + b
5:     return ret
6:   }
7:   return addNumbers
8: }
9: let adder = createAdder()
10: let sum = adder(val, 8)
11: console. log('example of function returning a function: ', sum)
  1. Строка 1. Объявлена переменная val в глобальном контексте и ей назначили значение 7.
  2. Строки 2–8. Объявлена переменная createAdder в глобальном контексте и ей назначили определение функции. Строки с 3 по 7 определяют тело функции. Как и ранее на этом шаге сама функция не запускается. Только сохраняется ее определение в переменной createAdder.
  3. Строка 9. Объявляется новая переменная с именем adder, в глобальном контексте. Временно ее значение в текущий момент undefined.
  4. Все еще строка 9. Так как мы видим скобки (); мы запускаем функцию на выполнение. В глобальном контексте ищется переменная createAdder. Она была определена в строке 2. Далее запускается функцию.
  5. Строка 2. Создается новый локальные контекст. Движок JavaScript добавляет новый контекст в стек выполнения.
  6. Строки 3–6. Объявляется новая функция. В локальном контексте создается новая переменная addNumbers. Это важно! addNumbers существует только в локальном контексте. Далее определение функции сохраняется в локальной переменной  addNumbers.
  7. Строка 7. Возвращается содержимое переменной addNumbers. В ней находится определение функции (строки с 4 по 5). Далее удаляется локальный контекст из стека вызовов.
  8. Удаляется локальный контекст и переменная addNumbers более не существует. Однако определение функции все еще существует, он возвращается из функции и назначается переменной adder; это переменная было создана в строке 3.
  9. Строка 10. Определяется новая переменная sum в глобальном контексте. И ей временно назначается значение undefined.
  10. Далее нам нужно выполнить функцию которая была определена в переменной adder. Движок ищет и находит ее в глобальном контексте. У этой функции есть два параметра.
  11. Давайте разберемся с параметрами. Первый переменная val определена в строке 1, ее значение 7, вторая переменная просто значение 8.
  12. Далее идет выполнение функции. Функция определена в строках 3–5. Вначале создается новый локальный контекст. Внутри локального контекста создается две новые переменные: a и b. У них значения 7 и 8 соотвественно.
  13. Строка 4. В новом локальном контексте объявляется новая переменная ret.
  14. Строка 4. Выполняется сложение переменных a и b. Результат сложения (15) назначается переменной ret.
  15. Переменная ret возвращается из функции. Локальный контекст уничтожается и удаляется из стека выполнения, переменные ab и ret более не существует.
  16. Возвращаемое значение назначается переменной sum определенной в строке 9.
  17. Значение переменной sum выводится в консоле.

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

Ну и наконец, замыкание

Посмотрим на следующий пример и попытайтесь понять, что здесь происходит.

1: function createCounter() {
2:   let counter = 0
3:   const myFunction = function() {
4:     counter = counter + 1
5:     return counter
6:   }
7:   return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console. log('example increment', c1, c2, c3)

Теперь, когда мы познакомились с предыдущими двумя примерами, давайте рассмотрим выполнение этого.

  1. Строки 1–8. Создается новая переменная createCounter в глобальном контексте и ей назначается определение функции.
  2. Строка 9. В глобальном контексте объявляется новая переменная increment .
  3. Строка 9. Вызывается функция createCounter и ее возвращаемое значение назначается переменной increment.
  4. Строки 1–8. Вызов функции. Создается новый локальный контекст.
  5. Строка 2. Внутри локального контекста, объявляется новая переменная counter. Ей назначается значение 0.
  6. Строки 3–6. В локальном контексте объявляется новая переменная myFunction. Значение переменной объявление еще одной функции (строки 4 и 5).
  7. Строка 7. Возвращается содержимое переменной myFunction. Локальный контекст удаляется. Переменные myFunction и counter больше не существуют. Контроль передается в вызывающий контекст.
  8. Строка 9. Вызывающий контекст в данном случае является глобальным контекстом, значение возвращаемое createCounter назначается increment. Сейчас в этой переменной определение функции. И оно больше не помечено именем myFunction, внутри глобального контекста оно названо increment.
  9. Строка 10. Объявляется новая переменная (c1).
  10. Строка 10. Ищется переменная increment, после того как оно будет найдено и определено что это функция, она будет запущена.
  11. Создается новый контекст. Так как у функции нет параметров перейдем к ее выполнению.
  12. Строка 4. counter = counter + 1. Начинается поиск  counter в локальном контексте. Мы только что его создали и в нем пока еще ничего нет. Далее ищется в глобальном контексте. Но там тоже нет переменной counter. Javascript будет рассматривать это выражение как  counter = undefined + 1, далее объявит новую локальную переменную counter и назначит ей значение 1, так как undefined воспринимается как 0.
  13. Строка 5. Далее возвращается переменная counter, а точнее ее значение 1. Уничтожается локальный контекст и переменная counter.
  14. Строка 10. Возвращаемое значение (1) назначается переменной c1.
  15. Строка 11. Мы повторяем строки 10–14, c2 получает 1.
  16. Строка 12. Снова повторяются строки 10–14, c3 получает 1.
  17. Строка 13. Выводятся переменные c1c2 и c3.

Попробуйте сами запустить этот пример и посмотрите, что получится. Вы заметите, что на самом деле выводится не  11, и 1 как мы только что рассмотрели выше. Вместо этого выводится 12 и 3. Так что здесь происходит?

Каким-то образом функция increment запоминает значение счетчика.

Переменная counter находится в глобальном контексте ? Попытайтесь выполнить console. log(counter)и вы получите counter is not defined. Так что нет, не находится.

Может быть, когда мы вызываем increment, каким-то образом он возвращается к функции, в которой он был создан (createCounter)? Как это вообще работает? Переменная increment содержит определение функции, а не то откуда она вызвалась.

Так что должен быть другой механизм. И он называется Замыкание Мы наконец дошли до этого!

Вот как это работает. Всякий раз, когда вы объявляете новую функцию и присваиваете ее переменной, вы сохраняете определение функции, а также замыкание. Замыкание содержит все переменные, которые находятся в области действия на момент создания функции. Это аналог рюкзака. Определение функции идет как бы с «небольшим рюкзаком». И в своем «рюкзаке» оно хранит все переменные, которые находились в области действия (scope) на момент создания определения функции.

Итак, наше объяснение выше было неверным, давайте попробуем еще раз, но на этот раз правильно.

1: function createCounter() {
2:   let counter = 0
3:   const myFunction = function() {
4:     counter = counter + 1
5:     return counter
6:   }
7:   return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)
  1. Строки 1–8. В глобальном контексте создается новая переменная createCounter и ей назначается определение функции.
  2. Строка 9. В глобальном контексте объявляется новая переменная increment.
  3. Строка 9. Вызывается функция createCounter и ее возвращаемое значение передается переменной increment .
  4. Строки 1–8 . Вызов функции. Создается новый локальный контекст.
  5. Строка 2. Внутри локального контекста, объявляется новая переменная counter. Ее назначается значение 0.
  6. Строка 3–6. В локальном контексте объявляется новая переменная myFunction. Переменная содержит объявление еще одной функции строки 4 и 5. Сейчас так же создается замыкание и включается в определение функции. Замыкание содержит переменные, находящиеся в области видимости, в данном случае переменная  counter  (со значением  0).
  7. Строка 7. Возвращается содержимое переменной myFunction. Локальный контекст удаляется. Переменные myFunction и counter перестают существовать. Контроль передается в вызывающий контекст. Таким образом, мы возвращаем определение функции и ее замыкание, «рюкзак» с переменными, которые находились в области видимости в момент ее создания.
  8. Строка 9. В вызывающем контексте, то есть в глобальном возвращаемое значение функцией createCounter передается в переменную increment. Переменная increment сейчас содержит определение функции (и замыкание).
  9. Строка 10. Объявляется переменная c1.
  10. Строка 10. Ищется переменная increment, после того как она будет найдена будет определенно ее содержимое. Она содержит определение функции в строках 4–5. (а так же содержит «рюкзак» с переменными)
  11. Создается новый контекст выполнения. У функции нет параметров поэтому сразу переходим к ее выполнению.
  12. Строка 4. counter = counter + 1. Ищется переменная counter. Прежде чем начнется поиск в локальном и глобальном контексте давай те посмотрим что в нашем «рюкзаке». Точнее посмотрим что в замыкании. А вот замыкание содержит переменную  counter, со значением 0. Поэтому выражению в строке 4, назначается значение 1. И это значение сохраняется в «рюкзаке». Замыкание сейчас содержит переменную counter со значением 1.
  13. Строка 5. Возвращается содержимое переменной counter, а точнее ее значение 1. Далее уничтожается локальный контекст.
  14. Строка 10. Возвращаемое значение (1) назначается переменной c1.
  15. Строка 11. Повторяются строки 10–14. На этот раз, когда мы смотрим на наше замыкание, мы видим, что переменная  counter  имеет значение 1. Она была установлена в строке 12 или строке 4 программы. Его значение увеличивается и сохраняется со значением 2  при закрытии функции increment. И переменная  c2 получает значение 2.
  16. Строка 12. Повторяются строки 10–14, c3 получает значение 3.
  17. Строка 13. Выводится значение переменных c1c2 и c3.

Итак, теперь мы понимаем, как это работает. Необходимо помнить, что когда функция объявляется, она содержит определение функции и замыкание. Так что такое замыкание?

Замыкание — это коллекция всех переменных в области видимости во время создания функции.

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

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

Не самые обычные замыкания

Иногда замыкания появляются, когда вы даже не замечаете этого. Возможно, вы видели похожий пример:

let c = 4
const addX = x => n => n + x
const addThree = addX(3)
let d = addThree(c)
console. log('example partial application', d)

В случае, если стрелочные функции сбивает вас с толку, напишем эквивалент.

let c = 4
function addX(x) {
  return function(n) {
     return n + x
  }
}
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)

Мы объявляем функцию сумматора addX, которая принимает один параметр (x) и возвращает другую функцию.

Возвращаемая функция также принимает один параметр и добавляет его к переменной x.

Переменная x является частью замыкания. Когда переменная  addThree  объявляется в локальном контексте, ей назначается определение функции и замыкание. Замыкание содержит переменную  x.

Так что теперь, когда  addThree  вызывается и выполняется, она имеет доступ к переменной  x из своего замыкания и переменной n, которая была передана в качестве аргумента.

В этом примере в консоль будет выведено число 7.

Заключение

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

Статья написана на основе I never understood JavaScript closures

Была ли вам полезна эта статья?

[1 / 5]


Spread the love

Онлайн-калькулятор

Онлайн-калькулятор Math можно использовать для проверки своего решения по многим математическим и экономическим дисциплинам. Результат решения — это отчет в формате Word (и Excel при необходимости), содержащий ход решения с комментариями, исходные формулы и выводы.

Теория вероятностей и математическая статистика

Математическое ожидание дискретной случайной величины: нахождение дисперсии и среднеквадратического отклонения
Корреляционная таблица: ковариация и уравнения регрессии
Системы случайных величин: X и Y
Выборочный метод: оценка среднего значения, дисперсия, доверительные интервалы.
Другие калькуляторы

Информатика

Перевод чисел из одной системы счисления в другую
Формат чисел с плавающей точкой
Сложение двоичных чисел
Таблица истинности: построение СКНФ и СДНФ с картами Карно (Вейча), минимизация булевой функции
Построение логической схемы (графически)
Другие калькуляторы

Линейная алгебра

Ранг матрицы
Обратная матрица через алгебраические дополнения .Определение миноров матрицы, алгебраических дополнений, транспонированной матрицы
Обратная матрица методом Жордано-Гаусса

Методы нахождения определителей: разложением по строкам и столбцам, методом треугольников, методом Гаусса (метод приведения к треугольному виду), методом декомпозиции

Умножение матриц
Преобразование матрицы до треугольной
LU разложение матрицы
Другие калькуляторы

Методы решения СЛАУ

Исследование системы линейных уравнений (на совместность и определенность)
Решения СЛАУ методом Гаусса (а также Жордано-Гаусса)
Решения СЛАУ методом Крамера
Решения СЛАУ методом обратной матрицы
Решения СЛАУ методом простой итерации
Решения СЛАУ методом Зейделя
Другие калькуляторы

Методы оптимизации

Метод Ньютона (метод дихотомии, модифицированный метод Ньютона, метод хорд, комбинированный метод, метод золотого сечения, метод итераций).
Метод множителей Лагранжа.
Все сервисы

Аналитическая геометрия

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

Математический сервис

Формула дискриминанта. Корни квадратичной функции.
Найти корни уравнения
Решить дифференциальные уравнения
Вычислить интеграл
Найти производную
Разложить на множители
Найти предел
Построить график функции
Градиент
Построить график функции методами дифференциального исчисления
Комплексные числа

Другие калькуляторы

Линейное программирование

Графический метод решения задач линейного программирования. Геометрический способ решения.
Решение симплексным методом (М-метод, двухэтапный метод, двухфазный метод)
Двойственный симплекс-метод (P-метод)
Двойственная задача линейного программирования

Транспортная задача
Задача коммивояжера
Задача о назначениях

Другие калькуляторы

Целочисленное программирование

Метод Гомори. Метод отсечений.
Графический метод.
Метод ветвей и границ.
Все сервисы

Динамическое программирование

Задача оптимального распределения инвестиций
Задача замены оборудования
Метод прямой и обратной прогонки
Все сервисы

Сетевое планирование

Сетевая модель. Параметры сетевой модели (ранний срок свершения событий, поздний срок свершения события, резерв времени)
Разрез сети. Минимальный разрез сети. Максимальный поток сети.

Модели теории игр

Оптимальная стратегия. Цена игры, седловая точка.
Игры с природой. Критерии Максимакса, Байеса, Лапласа, Вальда, Сэвиджа и Гурвица.
Биматричные игры.

Другие калькуляторы

Системы и модели массового обслуживания

Одноканальные модели систем массового обслуживания: Одноканальная СМО с отказами в обслуживании, Одноканальная СМО с ограниченной длиной очереди, Одноканальная СМО с неограниченной очередью.
Многоканальные модели систем массового обслуживания: Многоканальная СМО с отказами в обслуживании, Многоканальная СМО с ограниченной длиной очереди, Многоканальная СМО с неограниченной очередью.

Другие калькуляторы

Статистика

Выявление тренда методом аналитического выравнивания: по прямой, по параболе, по экспоненте, степенной функции, по гиперболе.

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

Однофакторный дисперсионный анализ

Коэффициент ранговой корреляции Спирмена

Коэффициент Фехнера

Другие калькуляторы

Эконометрика

Уравнение парной линейной регрессии. Коэффициент корреляции. Статическая надежность регрессионного моделирования с помощью F- критерия Фишера и с помощью t-критерия Стьюдента.
Уравнение нелинейной регрессии. Экспоненциальная, степенная, показательная, равносторонняя гипербола.

Уравнение множественной регрессии. Матричный метод. Матрица парных коэффициентов корреляции
Уравнение множественной регрессии для двух переменных с помощью формул Крамера. Система уравнений.

Другие калькуляторы


Полезные советы при пользовании сервисом

Как передать функцию TypeScript в качестве параметра

Kealan Parr Подписаться Инженер-программист, технический писатель и член консорциума Unicode.

3 минуты чтения 882

Содержание

  • Что такое тип функции TypeScript?
  • Использование номеров аргументов в TypeScript
  • Пример набора функций в TypeScript

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

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

TypeScript предлагает нам фантастические преимущества добавления статических типов и проверок транспиляции, и это может помочь нам лучше документировать, какие типы переменных мы ожидаем в наших функциях — но что произойдет, если нам нужно передать функции?

Кажется довольно очевидным, что нам, конечно, нужно набирать эти функции, но как нам их набирать и как мы передаем функцию в TypeScript?

В этом руководстве вы узнаете о функциях TypeScript и о том, как передавать их в качестве параметра в свои приложения.

Что такое тип функции TypeScript?

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

Тип функции (примечание: эта ссылка перенаправляет на старые документы TypeScript, но у нее гораздо более наглядный пример, чем у новых) состоит из типов аргументов, которые принимает функция, и возвращаемого типа функции.

Мы можем проиллюстрировать это на очень простом примере:

 const stringify = (el: any): string => {return el + "" }
const numberify = (el : any) : number => { return Number(el) }
пусть тест = строка;
тест = нумеровать;
 

Приведенный выше пример, если он реализован на JavaScript, будет работать нормально и не будет иметь проблем.


Более 200 000 разработчиков используют LogRocket для улучшения цифрового взаимодействия

Подробнее →


Но теперь мы использовали TypeScript, ошибки возникают, когда мы пытаемся транспилировать наш код.

 - Тип '(el: any) => number' не может быть назначен типу '(el: any) => string'.
- Тип «число» нельзя присвоить типу «строка».
 

Выведенное здесь сообщение об ошибке довольно описательно: функции stringify и numberify не взаимозаменяемы.

Они не могут быть взаимозаменяемо присвоены переменной test , поскольку они имеют конфликтующие типы. Аргументы, которые они получают, одинаковы (один аргумент типа , любые ), но из-за того, что они возвращают разные типы, мы получаем ошибки.

Мы могли бы изменить здесь возвращаемые типы, чтобы доказать, что наша теория верна.

 const stringify = (el: любой): число => {возврат 1}
const numberify = (el : any) : number => { return Number(el) }
пусть тест = строка;
тест = нумеровать;
 

Приведенный выше код теперь работает, как и ожидалось, и единственное отличие состоит в том, что мы изменили функцию stringify , чтобы она соответствовала типу numberify 9.0055 функция. Действительно, возвращаемый тип ломал этот пример.

Использование номеров аргументов в TypeScript

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

Давайте сделаем один последний пример, чтобы расширить последний рабочий пример, который у нас был.

 const stringify = (el: любой, el2: число) : число => {возврат 1}
const numberify = (el : any) : number => { return Number(el) }
пусть тест = строка;
тест = нумеровать;
 

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

Однако этот пример не выдает никаких ошибок, и он полностью легитимен в TypeScript, потому что TypeScript реализует то, что называется утиным вводом.

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

Пример ввода наших функций в TypeScript

Теперь мы точно знаем, как создавать типы для наших функций. Нам просто нужно убедиться, что мы печатаем функции, которые мы передаем в TypeScript.

Давайте снова вместе проработаем неудачный пример.

 const parentFunction = (el : () ) : number => {return el() }
 

Приведенный выше пример не работает, но он отражает то, что нам нужно.

Нам нужно передать родительской функции обратный вызов, который можно будет вызвать позже. Итак, что нам нужно изменить здесь?

  1. Нам нужно ввести аргумент функции el
  2. Нам нужно ввести аргументы, которые мы передаем в функцию el (если они требуются)

После этого наш пример должен выглядеть так:

 const parentFunction = (el : () => any ) : number => { return el() }
 

Этот конкретный пример не требует аргументов, но если бы они были, то это выглядело бы так:

 const parentFunction = (el : (arg: string) => any ) : number => { return el("Hello :)") }
 

Этот пример относительно прост, чтобы легко объяснить концепции функций TypeScript, но если у вас есть более сложные типы, вы можете потратить много времени на ввод всего.

Сообщество поддерживает множество высококачественных типов с открытым исходным кодом, обычно используемых в TypeScript, называемых DefinitelyTyped, которые могут помочь вам упростить и ускорить набор текста, который вам понадобится.

Заключение

Я надеюсь, что эта статья была полезной, и вы лучше понимаете ландшафт TypeScript, связанный с передачей функций в качестве аргументов другим функциям.

Обратные вызовы

обычно полагаются на этот метод, поэтому вы часто увидите интенсивное использование обратных вызовов в любой зрелой кодовой базе TypeScript. Удачного кодирования!

Много писать на TypeScript? Посмотрите запись нашей недавней встречи по TypeScript, чтобы узнать, как писать более читаемый код.

TypeScript обеспечивает безопасность типов в JavaScript. Между безопасностью типов и удобочитаемым кодом может быть противоречие. Посмотрите запись, чтобы подробно ознакомиться с некоторыми новыми функциями TypeScript 4.4.

функций | Котлин

Функции Kotlin объявляются с использованием ключевого слова fun :

fun double(x: Int): Int { возврат 2 * х }

Использование функций

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

val result = double(2)

Вызов функций-членов использует запись через точку:

Stream(). read() // создаем экземпляр класса Stream и call read()

Параметры

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

fun powerOf(number: Int, exponent: Int): Int { /*...*/ }

Вы можете использовать запятую при объявлении функции параметры:

удовольствие powerOf( номер: инт, показатель степени: Int, // запятая в конце ) { /*...*/ }

Аргументы по умолчанию

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

прикольно читать( б: массив байтов, выкл.: Int = 0, len: Int = b.size, ) { /*...*/ }

Значение по умолчанию определяется с использованием = после типа.

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

open class A { open fun foo(i: Int = 10) { /*...*/ } } класс Б: А () { override fun foo(i: Int) { /*...*/ } // Значение по умолчанию не разрешено. }

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

fun foo( бар: Int = 0, баз: инт, ) { /*...*/ } foo(baz = 1) // Используется значение по умолчанию bar = 0

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

fun foo( бар: Int = 0, баз: Инт = 1, qux: () -> Единица измерения, ) { /*...*/ } foo(1) { println("hello") } // Использует значение по умолчанию baz = 1 foo(qux = { println("hello") }) // Используются оба значения по умолчанию bar = 0 и baz = 1 foo { println("hello") } // Используются оба значения по умолчанию: bar = 0 и baz = 1

Именованные аргументы

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

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

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

весело переформатировать( ул: Строка, normalizeCase: Boolean = истина, upperCaseFirstLetter: Boolean = истина, разделитьByCamelHumps: Boolean = false, разделитель слов: символ = ' ', ) { /*...*/ }

При вызове этой функции вам не нужно называть все ее аргументы:

переформатировать( "Нить!", ЛОЖЬ, upperCaseFirstLetter = ложь, разделитьByCamelHumps = правда, '_' )

Можно пропустить все со значениями по умолчанию:

переформатировать("Это длинная строка!")

Вы также можете пропустить определенные аргументы со значениями по умолчанию, а не опускать их все. Однако после первого пропущенного аргумента вы должны назвать все последующие аргументы:

переформатировать("Это короткая строка!", upperCaseFirstLetter = false, wordSeparator = '_')

Вы можете передавать переменное количество аргументов ( vararg ) с именами, использующими оператор spread :

fun foo(vararg strings: String) { /*...*/ } foo(strings = *arrayOf("a", "b", "c"))

В JVM: нельзя использовать синтаксис именованных аргументов при вызове функций Java, поскольку байт-код Java не всегда сохраняет имена параметров функции.

Функции, возвращающие единицы измерения

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

fun printHello(name: String?): Unit { если (имя != ноль) println("Здравствуйте, $имя") еще println("Привет!") // `return Unit` или `return` необязательны }

Объявление типа возвращаемого значения Unit также является необязательным. Приведенный выше код эквивалентен следующему:

fun printHello(name: String?) { ... }

Функции с одним выражением

Когда функция возвращает одно выражение, фигурные скобки можно опустить, а тело указать после a = символ:

fun double(x: Int): Int = x * 2

Явное объявление возвращаемого типа необязательно, если это может быть определено компилятором:

fun double(x: Int) = x * 2

Явные возвращаемые типы

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

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

Переменное количество аргументов (varargs)

Параметр функции (обычно последний) можно пометить модификатором vararg :

fun asList(vararg ts: T): List { val результат = ArrayList() for (t in ts) // ts — это массив результат. добавить(т) вернуть результат }

В этом случае вы можете передать функции переменное количество аргументов:

val list = asList(1, 2, 3)

Внутри функции vararg -параметр типа T виден как массив T , как в примере выше, где переменная ts имеет тип Array .

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

Когда вы вызываете vararg -функцию, вы можете передавать аргументы по отдельности, например asList(1, 2, 3) . Если у вас уже есть массив и вы хотите передать его содержимое в функцию, используйте оператор spread (с префиксом * перед массивом):

val a = arrayOf(1, 2, 3) val list = asList(-1, 0, *a, 4)

Если вы хотите передать массив примитивного типа в vararg , вам нужно преобразовать его в обычный (типизированный) массив с помощью toTypedArray() function:

val a = intArrayOf(1, 2, 3) // IntArray — массив примитивного типа val list = asList(-1, 0, *a. toTypedArray(), 4)

Инфиксная нотация

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

  • Они должны быть функциями-членами или функциями расширения.

  • У них должен быть один параметр.

  • Параметр не должен принимать переменное количество аргументов и не должен иметь значения по умолчанию.

инфикс fun Int.shl(x: Int): Int { ... } // вызов функции с использованием инфиксной записи 1 шл 2 // такой же как 1.shl(2)

Вызовы инфиксных функций имеют более низкий приоритет, чем арифметические операторы, приведения типов и оператор rangeTo . Следующие выражения эквивалентны:

  • 1 shl 2 + 3 is equivalent to 1 shl (2 + 3)

  • 0 until n * 2 is equivalent to 0 until (n * 2)

  • xs union ys as Set<*> эквивалентен xs union (ys as Set<*>)

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

Обратите внимание, что инфиксные функции всегда требуют указания как получателя, так и параметра. Когда вы вызываете метод текущего приемника, используя инфиксную нотацию, явно используйте this . Это необходимо для обеспечения однозначного синтаксического анализа.

класс MyStringCollection { infix fun add(s: String) { /*...*/ } забавная сборка () { это добавить "abc" // Правильно добавить("abc") // Правильно //добавляем "abc" // Неверно: должен быть указан получатель } }

Область действия функции

Функции Kotlin могут быть объявлены на верхнем уровне в файле, что означает, что вам не нужно создавать класс для хранения функции, что требуется в таких языках, как Java, C# и Scala. В дополнение к функциям верхнего уровня функции Kotlin также могут быть объявлены локально как функции-члены и функции расширения.

Локальные функции

Kotlin поддерживает локальные функции, которые являются функциями внутри других функций:

fun dfs(graph: Graph) { fun dfs (текущий: Vertex, посещенный: MutableSet) { если (!visited. add(current)) вернуть for (v в current.neighbours) dfs(v, посетил) } dfs(graph.vertices[0], HashSet()) }

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

fun dfs(graph: Graph) { val посещено = HashSet() весело dfs (текущий: вершина) { если (!visited.add(current)) вернуть for (v в current.neighbours) dfs(v) } dfs (граф. вершины [0]) }

Функции-члены

Функция-член — это функция, определенная внутри класса или объекта:

класс Образец { весело foo() { print("Foo") } }

Функции-члены вызываются с записью через точку:

Sample().foo() // создает экземпляр класса Sample и вызывает foo

Дополнительные сведения о классах и переопределяющих членах см. в разделе Классы и наследование.

Общие функции

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

fun singletonList(item: T): List { /*. ..*/ }

Дополнительные сведения об универсальных функциях см. в разделе Универсальные функции.

Функции хвостовой рекурсии

Kotlin поддерживает стиль функционального программирования, известный как хвостовая рекурсия. Для некоторых алгоритмов, которые обычно используют циклы, вы можете вместо этого использовать рекурсивную функцию без риска переполнения стека. Когда функция помечена модификатором tailrec и соответствует требуемым формальным условиям, компилятор оптимизирует рекурсию, оставляя вместо нее быструю и эффективную версию, основанную на циклах:-15 tailrec fun findFixPoint(x: Double = 1.0): Double = if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))

Этот код вычисляет фиксированную точку косинуса, которая является математической константой. Он просто вызывает Math.cos , начиная с 1.0 , пока результат не перестанет меняться, давая результат 0. 7390851332151611 для указанной точности eps . Полученный код эквивалентен этому более традиционному стилю: 9-15 приватная забава findFixPoint(): Double { переменная х = 1,0 пока (правда) { знач y = Math.cos(x) если (Math.abs(x - y) < eps) вернуть x х = Math.cos (х) } }

Чтобы иметь право на модификатор tailrec , функция должна вызывать себя в качестве последней операции, которую она выполняет. Вы не можете использовать хвостовую рекурсию, когда после рекурсивного вызова есть больше кода, в блоках try / catch / finally или в открытых функциях. В настоящее время хвостовая рекурсия поддерживается Kotlin для JVM и Kotlin/Native.

См. Также :

  • Встроенные функции

  • Функции расширения

  • Высшие функции и Lambdas

Последние модифицированные: 06 сентября 2022

Понимание функций JavaScript | Зелл Лью

8 ноября 2017 г.

Представьте, что вы живете в деревне без водопроводной воды. Чтобы набрать воды, нужно взять пустое ведро, направиться к колодцу посреди деревни, набрать воды из колодца и вернуться домой.

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

Сокращенно можно сказать, что вы собираетесь «черпать воду».

Друг мой, ты создал функцию.

Объявление функций

Функция — это блок кода, который выполняет задачи в определенном порядке, например, взять пустое ведро, пойти к колодцу, набрать воды, вернуться домой.

Его можно определить с помощью следующего синтаксиса:

 функция имя_функции (параметры) {
  // Делаем что-то здесь
}
 

функция — это ключевое слово, которое сообщает JavaScript, что вы определяете функцию.

имя_функции — имя функции. В приведенном выше примере имя функции может быть drawWater .

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

  1. Должно быть одно слово
  2. Должен состоять только из букв, цифр или знаков подчеркивания (0-9, a-z, A-Z, _ ).
  3. Не может начинаться с цифры.
  4. Это не может быть ни одно из этих зарезервированных ключевых слов

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

Использование функций

Как только вы объявили свою функцию, вы можете использовать ( или вызвать, или вызвать, или выполнить ) его, написав имя функции, за которым следует скобка () .

Вот пример объявления и использования функции sayHello .

 // Объявление функции
функция сказать привет () {
  console.log('Привет, мир!')
}
// использование функции
скажи привет()
 
Объявление и использование функции sayHello

Отступ

Код внутри блока (все, что заключено в фигурные скобки {} ) должен иметь отступ вправо. Это важная практика, которая поможет вам сделать код более удобным для чтения. Это позволяет с первого взгляда сказать, что console.log('Hello world') является частью sayHello .

 функция сказать привет () {
  // Этот оператор console.log является частью sayHello
  console.log('Привет, мир!')
}
 

Вы можете выбрать отступ с двумя пробелами или с помощью клавиши табуляции. Кто-то предпочитает пробелы, кто-то табуляцию. Оба хороши, пока вы держите это последовательно.

Параметры

Большинство функций принимают параметры. Это список переменных, разделенных запятыми , которые вы хотите объявить для своей функции.

У вас может быть любое количество параметров.

 функция имя_функции (параметр1, параметр2, параметр3) {
  // Делаем что-то здесь
}
 

Чтобы присвоить значения вашим параметрам, вы передаете значения (называемые аргументами), записывая их как значения, разделенные запятыми, в скобках

Первый аргумент будет присвоен первому параметру, второй аргумент — второму параметру и так далее.

 имя_функции('arg1', 'arg2')
 

Поясним это на примере.

Допустим, вы хотите написать функцию с именем sayName , которая регистрирует имя и фамилию человека. Функция выглядит так:

 функция sayName(firstName, lastName) {
  console.log('firstName is ' + firstName)
  console.log('фамилия равна ' + фамилия)
}
 

Зелл — мое имя, Лью — моя фамилия. Чтобы функция работала правильно, я передаю свой Zell в качестве первого аргумента и Liew в качестве второго аргумента:

 sayName('Зелл', 'Лью')
// firstName — Zell
// фамилия это Лью
 

Если вы объявили параметр, но не передали ему аргумент, ваш параметр будет undefined .

 сказатьИмя()
// firstName не определено
// фамилия не определена
 

Функции могут иметь оператор return, состоящий из ключевого слова return и значения:

 функция имя_функции () {
  вернуть «какое-то значение»
}
 

Когда JavaScript видит этот оператор return, он прекращает выполнение остальной части функции и «возвращает» (передает значение обратно вызову функции).

 функция get2 () {
  вернуть 2
  console.log('blah') // Это не выполняется
}
константные результаты = get2()
console.log(результаты) // 2
// Примечание: вы не увидите в консоли 'blah'
 

Если возвращаемое значение является выражением, JavaScript оценивает выражение перед возвратом значения.

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

Поток функции

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

Вот код, который мы анализируем:

 функция add2 (число) {
  вернуть число + 2
}
константное число = add2(8)
console.log(число) // 10
 

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

В этот момент код функции пропускается, поскольку функция еще не используется.

JavaScript видит add2 и пропускает его

Затем JavaScript видит, что вы объявляете переменную с именем number и назначаете ее как результат add2(8) .

Поскольку правая часть (RHS) является вызовом функции (выражением), JavaScript должен оценить значение add2(8) , прежде чем он сможет присвоить его переменной номер . Здесь параметру num присваивается значение 8 , так как вы передали 8 в качестве аргумента при вызове add2(8) .

JavaScript выполняет функцию add2

В функции add2 JavaScript видит оператор возврата, который говорит num + 2 . Это выражение, поэтому его необходимо вычислить, прежде чем двигаться дальше. Поскольку число равно 8, число + 2 должно быть 10.

JavaScript оценивает число + 2 как 10

После вычисления num + 2 JavaScript возвращает значение вызову функции. Он заменяет вызов функции возвращаемым значением. Итак, add2(8) становится 10.

JavaScript заменяет add2(8) на результат 10

. Наконец, после оценки RHS, JavaScript создает переменную с номером и присваивает ей значение 10.

Вот как вы читаете поток функции.

Подъемный

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

 функция сказать привет () {
  console.log('Привет, мир!')
}
скажи привет()
 
 // Это автоматически преобразуется в приведенный выше код
скажи привет()
функция сказать привет () {
  console.log('Привет, мир!')
}
 

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

Объявление функций с функциональными выражениями

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

 const sayHello = функция () {
  console.log('Это объявлено с помощью функционального выражения!')
}
 

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

 sayHello () // Ошибка, sayHello не определено
const sayHello = функция () {
  console.log('это функция!')
}
 

В этот момент вы можете задаться вопросом, важны ли функциональные выражения. Это общий вопрос. Зачем вам использовать функциональные выражения, если вы можете объявлять функции с синтаксисом объявления функций?

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

Подведение итогов

Функция — это блок кода, который выполняет задачи в определенном порядке, например, взять пустое ведро, пойти к колодцу, набрать воды, вернуться домой.

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

Каждая функция может иметь оператор return, который «возвращает» значение в вызов функции.

Насколько это возможно, не полагайтесь на подъем при написании функций.