В чем проблема?
У нас есть функция, которая проверяет какое-то особое условие и возвращает первое значение,
подходящее под условие. Если завтра мне нужно будет ответить на вопрос
«Какое первое число Фибоначчи делится на 19?»,
или
«Какое первое число Фибоначчи, которое является квадратом другого числа?»,
я могу просто скопировать код и изменить условие.
Звучит просто, но это значит, что у нас будет множество копий кода, реализующего функцию fibonacci.
Что если алгоритм будет намного сложнее? Мы все еще хотим копировать тот код снова и снова?
Что если мы найдем ошибку в нашем алгоритме (или просто способ реализации лучше)
после 20 копирований для ответов на разные вопросы?
Это явно не очень хороший путь.
Давайте попробуем другой подход.
Изменим функцию Fibonacci так, чтобы она принимала функцию как параметр и вызывала эту функцию
для каждого элемента последовательности Фибоначчи.
Возвращаемые значения (return)
Распаковка возвращаемых значений
В Питоне поддерживается возврат функциями сразу несколько значений. Достаточно перечислить их через запятую после инструкции . Возвращаемым типом будет кортеж (), который можно распаковать в переменные.
️ Обратите внимание, что количество возвращаемых значение в кортеже должно совпадать с количеством переменных при распаковке. Иначе произойдет ошибка:
Пустая функция
Иногда разработчики оставляют реализацию на потом, и чтобы объявленная функция не генерировала ошибки из-за отсутствия тела, в качестве заглушки используется ключевое слово
Дизайн [ править ]
Существует два типа обратных вызовов, различающихся тем, как они управляют потоком данных во время выполнения: блокирующие обратные вызовы (также известные как синхронные обратные вызовы или просто обратные вызовы ) и отложенные обратные вызовы (также известные как асинхронные обратные вызовы ). Хотя блокирующие обратные вызовы вызываются перед возвратом функции (в приведенном ниже примере C, который иллюстрирует блокирующий обратный вызов, это функция main), отложенные обратные вызовы могут быть вызваны после возврата из функции. Отложенные обратные вызовы часто используются в контексте операций ввода-вывода или обработки событий и вызываются прерываниями или другим потоком в случае нескольких потоков. Из-за своей природы блокирующие обратные вызовы могут работать без прерываний или нескольких потоков, что означает, что блокирующие обратные вызовы обычно не используются для синхронизации или делегирования работы другому потоку.
Обратные вызовы используются для программирования приложений в оконных системах . В этом случае приложение предоставляет (ссылку на) конкретную настраиваемую функцию обратного вызова для вызова операционной системы, которая затем вызывает эту специфичную для приложения функцию в ответ на такие события, как щелчки мыши или нажатия клавиш. Основной проблемой здесь является управление привилегиями и безопасностью: пока функция вызывается из операционной системы, она не должна выполняться с теми же привилегиями, что и система. Решение этой проблемы — использование колец защиты.
Проверяет, что все элементы в последовательности True.
Описание:
Функция возвращает значение , если все элементы в итерируемом объекте — истинны, в противном случае она возвращает значение .
Если передаваемая последовательность пуста, то функция также возвращает .
Функция применяется для проверки на ВСЕХ значений в последовательности и эквивалентна следующему коду:
def all(iterable): for element in iterable if not element return False return True
Так же смотрите встроенную функцию
В основном функция применяется в сочетании с оператором ветвления программы . Работу функции можно сравнить с оператором в Python, только работает с последовательностями:
>>> True and True and True # True >>> True and False and True # False >>> all() # True >>> all() # False
Но между и в Python есть два основных различия:
- Синтаксис.
- Возвращаемое значение.
Функция всегда возвращает или (значение )
>>> all() # True >>> all(]) # False
Оператор , возвращает ПОСЛЕДНЕЕ истинное значение, при условии, что в выражении все значения а если в выражении присутствует значение (ложное значение), то ПЕРВОЕ ложное значение. Что бы добиться поведения как у функции , необходимо выражение с оператором обернуть в функцию .
>>> 3 and 1 and 2 and 6 # 6 >>> 3 and and 3 and [] # 0 >>> bool(3 and 1 and 2 and 6) # True >>> bool(3 and and 3 and []) # False
Из всего сказанного можно сделать вывод, что для успешного использования функции необходимо в нее передавать последовательность, полученную в результате каких то вычислений/сравнений, элементы которого будут оцениваться как или . Это можно достичь применяя функцию или выражения-генераторы списков, используя в них встроенные функции или методы, возвращающие значения, операции сравнения, оператор вхождения и оператор идентичности .
num = 1, 2.0, 3.1, 4, 5, 6, 7.9 # использование встроенных функций или # методов на примере 'isdigit()' >>> str(x).isdigit() for x in num # # использование операции сравнения >>> x > 4 for x in num # # использование оператора вхождения `in` >>> '.' in str(x) for x in num # # использование оператора идентичности `is` >>> type(x) is int for x in num # # использование функции map() >>> list(map(lambda x x > 1, num)) #
Примеры проводимых проверок функцией .
Допустим, у нас есть список чисел и для дальнейших операций с этой последовательностью необходимо знать, что все числа например положительные.
>>> num1 = range(1, 9) >>> num2 = range(-1, 7) >>> all() # True >>> all() # False
Или проверить, что последовательность чисел содержит только ЦЕЛЫЕ числа.
>>> num1 = 1, 2, 3, 4, 5, 6, 7 >>> num2 = 1, 2.0, 3.1, 4, 5, 6, 7.9 >>> all() # True >>> all() # False
Или есть строка с числами, записанными через запятую и нам необходимо убедится, что в строке действительно записаны только цифры. Для этого, сначала надо разбить строку на список строк по разделителю и проверить каждый элемент полученного списка на десятичное число методом . Что бы учесть правила записи десятичных чисел будем убирать точку перед проверкой строки на десятичное число.
>>> line1 = "1, 2, 3, 9.9, 15.1, 7" >>> line2 = "1, 2, 3, 9.9, 15.1, 7, девять" >>> all() # True >>> all() # False
Еще пример со строкой. Допустим нам необходимо узнать, есть ли в строке наличие открытой И закрытой скобки?
>>> simbols = '(', ')' >>> line1 = "функция 'all()' всегда возвращает 'False' или 'True'" >>> line2 = "функция any всегда возвращает значение bool" >>> all() # True >>> all() # False
Каким должен быть правильный Callback
Ненавязчивым. Злоупотребление всплывающими окнами на сайте отталкивает и раздражает покупателей. Виджет обратного звонка можно запрограммировать на показ через определенный промежуток времени. Для повышения лояльности клиентов лучше настроить callback-виджет на одно появление за сеанс, чем через каждые 20 секунд.
С возможностью скрыть. У виджета обратного звонка на сайте должна быть кнопка отказа, закрывающая всплывающее окно, чтобы не отвлекать клиента. Также виджет можно настроить таким образом, чтобы предложения на обратные звонки не высвечивались клиентам, которые уже совершили покупку или закрыли виджет в прошлый раз.
Анализируемым. Существуют сервисы, которые используют машинное обучение и искусственный интеллект для сбора данных о пользователях, источниках их перехода, проведенного времени на сайте и т.д. Callback-виджет может собирать статистику о звонках и с помощью подключенного колл-трекинга предоставлять информацию об источниках перехода и ключевых словах.
Потоки
Я хочу, чтобы вы вспомнили последний раз, когда вы ходили по магазинам за продуктами. Вероятно, для клиентов были открыты несколько кассовых аппаратов. Это помогает магазину обрабатывать больше транзакций за такое же количество времени. Это пример параллелизма.
Проще говоря, параллелизм выполняет одновременно несколько задач. Ваша операционная система является параллельной, поскольку одновременно выполняется несколько процессов. Процесс представляет собой среду исполнения или экземпляр запущенного приложения. Например, ваш браузер, текстовый редактор и антивирусное программное обеспечение — это все процессы на вашем компьютере, которые работают одновременно.
Приложения также могут быть параллельными. Это достигается с помощью потоков. Я не буду слишком глубоко в этом прогужаться, потому что это выходит за рамки этой статьи. Если вы хотите получить подробное объяснение того, как работает JavaScript под капотом, я рекомендую посмотреть это видео.
Поток — это единица внутри процесса, выполняющего код. В нашем примере магазина каждая касса будет потоком. Если в магазине будет только одна касса, это изменит процесс обработки клиентов.
Вы когда-нибудь были в очереди и что-то задержало вашу транзакцию? Может быть, вам нужна проверка цены или нужно было увидеть менеджера. Когда я нахожусь в почтовом отделении, пытаясь отправить посылку, и у меня нет заполненных ярлыков, кассир просит меня отойти в сторону, пока они продолжают проверять других клиентов. Когда я готов, я возвращаюсь к кассе, чтобы оплатить посылку.
Это похоже на то, как работает асинхронное программирование. Кассир смог дождаться меня. Иногда они это делают. Но лучше не тратить время и обрабатывать других клиентов. Дело в том, что клиенты не должны обрабатываться в том порядке, в котором они находятся в очереди. Аналогично, код не должен выполняться в том порядке, в котором мы его пишем.
4 ответа
Лучший ответ
ES6 поддерживает параметры деструктуризации. Ты можешь использовать:
Однако обычно это полезно, когда у вас есть несколько параметров:
Только старый IE не поддерживает это.
Вы не можете передать параметры таким способом или выполнить инициализацию параметра по умолчанию. Кроме того, в вашем случае будет ссылаться на родительский объект, поэтому не имеет смысла, поскольку в большинстве случаев он будет ссылаться на объект.
При деструктуризации параметров вы можете использовать параметры по умолчанию , поэтому ваш код будет выглядеть так:
Тем не менее, любые попытки вызвать его без параметра приведут к ошибке, поскольку JS попытается разрешить свойство
Вы можете использовать другое назначение параметров по умолчанию для решения проблемы. Если вы действительно хотите вызвать без параметров и иметь значение по умолчанию для деструктурированного параметра, вы должны использовать что-то вроде:
Только не забывайте, что он не широко поддерживается браузерами , поэтому вам придется использовать транспортер для преобразования кода ES6, поддерживаемого браузером.
Наиболее популярными транспортерами являются Babel и Машинопись.
7
v-andrew
9 Дек 2019 в 19:41
Во-первых, кажется, что вы комбинируете, как объявлять параметр функции, как вызывать функцию и передавать ей параметр с помощью этого кода:
Далее, при доступе к аргументам, просто используйте имя параметра, а не . используется для ссылки на объект контекста вызова, а не на параметры функции. Объект контекста вызова — это, по сути, объект, который был ответственен за то, что в первую очередь вызывал функцию. Он также может указывать на объект, который был явно установлен для замены собственного объекта, который был бы объектом контекста. И при правильных обстоятельствах он может указывать на глобальный () объект или быть . обычно сбивает с толку многих разработчиков JavaScript. Вот на дополнительную информацию о .
Итак, ваш код должен быть:
Теперь вы можете указать значение по умолчанию для функции, и это будет следующим образом:
2
Scott Marcus
14 Сен 2017 в 18:20
Причина this не имеет ничего общего с переданными переменными. Не используйте это здесь. Просто сделайте:
4
Jonas Wilms
14 Сен 2017 в 17:54
Ни один из ответов на самом деле не пытался решить эту проблему здесь, поэтому я подумал, что можно попробовать.
Вы спрашиваете: «Я хочу использовать объект в качестве аргумента функции». И все же ваш первый пример не показывает, как вы это делаете. Вы не передаете объект в качестве аргумента функции. То, что вы делаете , — это объявление переменной в (предполагается, что это код браузера), а затем запись значения этой переменной в консоль. Поскольку является контекстом , а определяется способом, которым функция называется , в данном случае контекст и ваш код работает.
Ваш второй пример кода объединяет ошибки, которые вы делаете в первом примере. имеет локальную область видимости для функции. Ему не нужно перед ним. И вы не можете получить к нему доступ, как будто это объект, потому что является массивоподобной структурой. Вы получите к нему доступ следующим образом: , если вы передаете объект в свою функцию следующим образом: .
Контекст и область действия JavaScript могут быть очень сложной концепцией, чтобы заставить вас задуматься. Я довольно опытный разработчик JS, и он до сих пор меня ловит, так что не расстраивайтесь. Эта статья очень хороша для объяснения контекста (и области действия функции) и я думаю, что вам будет полезно прочитать его.
2
Andy
14 Сен 2017 в 18:23
Спецификация оператора for/in/else:
for_stmt :: = "for" target_list "in" expression_list ":" suite "else" ":" suite
Список выражений для перебора инструкцией вычисляется один раз и должен давать объект поддерживающий итерацию. Итератор создается для результата . Каждый элемент из в свою очередь присваивается целевой переменной , значение которой передается в блок кода внутри инструкции . Затем код блока выполняется один раз для каждого элемента. Когда элементы исчерпаны, что происходит сразу же, когда последовательность пуста или итератор вызывает исключение , выполняется набор в предложении , если он присутствует, и цикл завершается.
- Оператор : выполняется код внутри до оператора и завершает цикл без выполнения блока внутри .
- Оператор : выполняется код внутри до оператора , пропускает оставшуюся часть кода в блоке и продолжает работу со следующим элементом списка перебираемых выражений или с оператором , если следующего элемента нет.
Применим оператор и в коде и посмотрим на их поведение. Будем создавать список четных чисел из последовательности чисел от 0 до 14.
lst = [] for item in range(15) # если число 10 есть в списке if 10 in lst # прерываем цикл, при этом блок else не выполнится break # остаток от деления элемента списка a = item % 2 # если элемент списка не четный или равен 0 if a != or item == # пропускаем оставшийся код continue # добавление числа в список lst.append(item) else print ("Напечатает, если убрать условие с break") print(lst) # Код выведет: 2, 4, 6, 8, 10
Цикл выполняет назначения переменным в целевом списке. Это перезаписывает все предыдущие назначения этим переменным, включая те, которые были сделаны в блоке :
Пример:
for i in range(10): print(i) i = 5 # это не повлияет на цикл for так как переменная i # будет перезаписана следующим итерируемым элементом
Имена в целевом списке не удаляются, когда цикл завершен, но если последовательность пуста, то код внутри цикла не будет выполнен.
Подсказка: встроенная функция возвращает итератор целых чисел, подходящий для эмуляции эффекта языка Pascal например, возвращает список .
Обратите внимание. Существует тонкость, когда последовательность, по которой проходит итерация, пытаются изменить внутри цикла
Существует внутренний счетчик, который используется для отслеживания того, какой элемент используется следующим, и он увеличивается на каждой итерации. Когда этот счетчик достигнет длины последовательности, цикл завершается. Это означает, что если код внутри цикла удаляет текущий (или предыдущий) элемент из последовательности, по которой идет итерация, следующий элемент будет пропущен, так как он получает индекс текущего элемента, который уже был обработан.
Аналогично, если код внутри цикла вставляет элемент в последовательность перед текущим элементом. Текущий элемент будет обработан снова на следующем этапе итерации. Это может привести к неприятным ошибкам, которых можно избежать, сделав временную копию с использованием фрагмента всей последовательности.
b = a for item in b if item < a.remove(item)
Как обратный звонок используется в бизнесе
Виджет обратного звонка используют компании, которые хотят наладить оперативную коммуникацию с клиентами и для которых звонки остаются одним из главных каналов коммуникации. Это может быть бизнес из разных сфер: финансовые услуги, недвижимость, туристический бизнес, медицина, автосалоны, юридические и консультационные компании.
Все эти сферы объединяют конкурентные условия на рынке, в которых ценным является каждый покупатель и важность живых консультаций. Турагентства в процессе обратного звонка могут подобрать индивидуальный тура, банки — озвучить условия по кредитам, строительные компании — рассчитать стоимость работ
В каждом случае компания нацелена на то, чтобы помочь клиенту решить его вопрос и смотивировать на покупку.
Написание анимированной функции
Далее вам надо написать функцию , которая будет ответственна за прорисовку кадров.
Скопируйте следующий код в ваш файл.
// Animate.function animate() {requestID = requestAnimationFrame(animate);// Проверка если блок не достиг конца отрисовки в канвасе.// В противном случае завершается анимация.if (posX <= (canvas.width — boxWidth)) {ctx.clearRect((posX — pixelsPerFrame), 0, boxWidth, canvas.height);ctx.fillRect(posX, 0, boxWidth, canvas.height);posX += pixelsPerFrame;} else {cancelAnimationFrame(requestID);}}
Вызов вверху функции запланирует следующий кадр анимации. Он размещается сначала, так как мы можем подобраться как можно ближе к 60 FPS, при использовании фолбэка, которое применяет полифил.
Далее у вас будет проверка, которая проверяет достиг ли блок крайней правой стороны canvas’а. Если блок ещё этого не сделал, то вы используете метод, чтобы удалить блок, отрисованный в предыдущем кадре и затем отрисовать блок на новой позиции с использованием . Если же блок достиг конца canvas’а, то вы вызываете , чтобы отменить планирование кадра в начале animate функции. И под конец, вы обновляете переменную с позицией на которой блок должен быть отрисован при следующем кадре.
setTimeout()
Эту функцию вы видели выше, а сейчас узнаете про неё ещё детальнее. Она используется в основном в тех случаях, если вы хотите запустить вашу функцию через конкретное количество миллисекунд после вызова самого . Синтаксис для этого метода такой:
setTimeout ( expression, timeout );
Тут в JavaScript коде запустится по прошествии миллисекунд, указанных в аргументе .
также возвращает для тайм-аута, чтобы его можно было отследить. Но в основном оно используется для метода , который останавливает выполнение отложенной функции. В качестве аргумента тут нужно вставить (название) функции.
Вот ещё один пример:
<input type="button" name="sayHello" value="Wait for my Hello!"onclick="setTimeout('alert(\'Hello!\')', 4000)"/>
При нажатии на кнопку запускается метод. Выражение, запуск которого по вашему предусмотрению должен произойти с задержкой в 4000ms или 4 секунды, уже передано.
Тут стоит обратить внимание на то, что не останавливает выполнение дальнейшего скрипта во время периода тайм-аута. Он просто откладывает выполнение указанного блока кода на заложенное количество времени
После вызова функции , скрипт продолжит выполняться обычным образом, с таймером на фоне.
То, что выше — это простой пример со всем кодом для alert бокса в вызове. На практике же, вы будете вызывать функции внутри таймеров гораздо чаще. Следующий пример даст вам лучшее понимание о вызове функций с помощью .
Для примера, код ниже, вызывает через одну секунду:
function sayHello() {alert('Hello');}setTimeout(sayHello, 1000);
Вы можете также передавать аргументы вместе с функцией, например, как тут:
function sayHello(message, person) {alert( message + ', '+ person );}setTimeout(sayHello, 1000, "Hi", "Monica"); // Hi, Monica
Как вы видите, для сначала передаётся функция аргумент, затем время задержки и уже только потом аргументы для функции аргумента(пардон за каламбур).
Если первый аргумент это строка, то JavaScript может создать из неё функцию. Так что вот это тоже сработает:
setTimeout("alert('Hello')", 1000);
Но применение такого метода не рекомендуется, лучше используйте функции, как тут:
setTimeout(() => alert('Hello'), 1000);
Ну так что ты делаешь?
Ответ заключается в том, чтобы оставаться в шаблоне обратного вызова. Всякий раз, когда вы хотите написать
и его следует называть асинхронно, вместо этого вы будете писать
где передается как обратный вызов.
Это принципиально изменяет поточную топологию вашей программы и требует некоторого привыкания.
Ваш язык программирования может помочь вам, предоставив вам способ создания функций «на лету». В коде, приведенном выше, функция может быть такой же небольшой . Если ваш язык требует, чтобы вы определили это как отдельную функцию, с совершенно ненужным именем и подписью, тогда ваша жизнь будет неприятной, если вы будете использовать этот шаблон много.
Если, с другой стороны, язык позволяет создавать лямбды, тогда вы находитесь в гораздо лучшей форме. Затем вы в конце концов напишите что-то вроде
что гораздо приятнее.
Как пройти обратный вызов
Как бы вы передали функцию обратного вызова ? Ну, вы могли бы сделать это несколькими способами.
-
Если вызываемая функция работает в том же процессе, вы можете передать указатель на функцию
-
Или, может быть, вы хотите сохранить словарь в своей программе, и в этом случае вы могли бы передать имя
-
Возможно, ваш язык позволяет вам определить функцию на месте, возможно, как лямбда! Внутри он создает какой-то объект и передает указатель, но вам не о чем беспокоиться.
-
Возможно, функция, которую вы вызываете, работает на совершенно отдельной машине, и вы вызываете ее с использованием сетевого протокола, такого как HTTP. Вы можете выставить свой обратный вызов как функцию, вызывающую HTTP, и передать ее URL.
Вы поняли эту идею.
Недавний рост обратных вызовов
В эту эпоху Интернета мы ввели, услуги, которые мы вызываем, часто происходят по сети. У нас часто нет контроля над этими услугами, т. Е. Мы их не пишем, мы их не поддерживаем, мы не можем гарантировать, что они работают или как они работают.
Но мы не можем ожидать, что наши программы будут блокироваться, пока мы ждем ответа этих служб. Зная об этом, поставщики услуг часто разрабатывают API-интерфейсы с использованием шаблона обратного вызова.
JavaScript поддерживает обратные вызовы очень хорошо, например, с помощью лямбда и закрытия. И в мире JavaScript много активности, как в браузере, так и на сервере. Существуют даже платформы JavaScript, разработанные для мобильных устройств.
Когда мы продвигаемся вперед, все больше и больше нас будут писать асинхронный код, для которого это понимание будет иметь важное значение
Используем requestAnimationFrame
Этому методу должна быть передана колбэк функция, которая отвечает за отрисовку одного кадра вашей анимации. Для того, чтобы создать полную анимацию, вам понадобится сделать этот колбэк рекурсивным.
Временная метка с высоким разрешением DOMHighResTimeStamp передаётся колбэку. Вам не понадобится всегда это использовать, но это может быть довольно полезным для некоторых анимаций.
Пример ниже показывает то, как настроить рекурсивную функцию, которая использует .
// Анимируемfunction animate(highResTimestamp) {requestAnimationFrame(animate);// Анимируем что-нибудь…}// Запускаем анимацию.requestAnimationFrame(animate);
Стоит упомянуть, что у вас есть только 16.67 миллисекунд, чтобы отрендерить каждый кадр. С точки зрения времени это не очень хорошо, так что вам нужно быть осторожным с тем, что вы хотите выполнить внутри колбэк функции. Если ваш кадр требует больше 16.67 секунд на обработку, то анимация может выйти не совсем плавной.
отдаст , который может быть использован для отмены запланированного кадра анимации.
var requestID = requestAnimationFrame(animate);
Ну так что ты делаешь?
Ответ заключается в том, чтобы оставаться в шаблоне обратного вызова. Всякий раз, когда вы хотите написать
и следует называть асинхронно, вместо этого вы будете писать
где передается как обратный вызов.
Это принципиально изменяет поточную топологию вашей программы и требует некоторого привыкания.
Ваш язык программирования может помочь вам, предоставив вам способ создания функций «на лету». В приведенном выше коде функция может быть такой же малой, как . Если ваш язык требует, чтобы вы определили это как отдельную функцию, с совершенно ненужным именем и подписью, тогда ваша жизнь будет неприятной, если вы будете использовать этот шаблон много.
Если, с другой стороны, язык позволяет создавать лямбды, тогда вы находитесь в гораздо лучшей форме. Затем вы в конце концов напишите что-то вроде
что гораздо приятнее.
Как пройти обратный вызов
Как бы вы передали функцию обратного вызова в ? Ну, вы могли бы сделать это несколькими способами.
-
Если вызываемая функция работает в том же процессе, вы можете передать указатель на функцию
-
Или, может быть, вы хотите сохранить в своей программе словарь , и в этом случае вы можете передать имя
-
Возможно, ваш язык позволяет вам определить функцию на месте, возможно, как лямбда! Внутри он создает какой-то объект и передает указатель, но вам не о чем беспокоиться.
-
Возможно, функция, которую вы вызываете, работает на совершенно отдельной машине, и вы вызываете ее с использованием сетевого протокола, такого как HTTP. Вы можете выставить свой обратный вызов как функцию, вызывающую HTTP, и передать ее URL.
Вы поняли эту идею.
Недавний рост обратных вызовов
В эту эпоху Интернета мы ввели, услуги, которые мы вызываем, часто происходят по сети. У нас часто нет контроля над этими услугами, т. Е. Мы их не пишем, мы их не поддерживаем, мы не можем гарантировать, что они работают или как они работают.
Но мы не можем ожидать, что наши программы будут блокироваться, пока мы ждем ответа этих служб. Зная об этом, поставщики услуг часто разрабатывают API-интерфейсы с использованием шаблона обратного вызова.
JavaScript поддерживает обратные вызовы очень хорошо, например, с помощью лямбда и закрытия. И в мире JavaScript много активности, как в браузере, так и на сервере. Существуют даже платформы JavaScript, разработанные для мобильных устройств.
Когда мы продвигаемся вперед, все больше и больше нас будут писать асинхронный код, для которого это понимание будет иметь важное значение