Как работает js: цикл событий, асинхронность и пять способов улучшения кода с помощью async / await

Цикл for-in

Цикл используется для перебора в случайном порядке перечисляемых свойств объекта и имеет следующий синтаксис:

for (переменная in объект)
  инструкция;

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

var obj = {x: 5, y: 10};

for (var prop in obj) {
  alert(prop);
}

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

С этой темой смотрят:

  • Инструкции break и continue
  • Метки инструкций
  • Выражения и инструкции
  • Преобразование типов данных

JavaScript

JS Array
concat()
constructor
copyWithin()
entries()
every()
fill()
filter()
find()
findIndex()
forEach()
from()
includes()
indexOf()
isArray()
join()
keys()
length
lastIndexOf()
map()
pop()
prototype
push()
reduce()
reduceRight()
reverse()
shift()
slice()
some()
sort()
splice()
toString()
unshift()
valueOf()

JS Boolean
constructor
prototype
toString()
valueOf()

JS Classes
constructor()
extends
static
super

JS Date
constructor
getDate()
getDay()
getFullYear()
getHours()
getMilliseconds()
getMinutes()
getMonth()
getSeconds()
getTime()
getTimezoneOffset()
getUTCDate()
getUTCDay()
getUTCFullYear()
getUTCHours()
getUTCMilliseconds()
getUTCMinutes()
getUTCMonth()
getUTCSeconds()
now()
parse()
prototype
setDate()
setFullYear()
setHours()
setMilliseconds()
setMinutes()
setMonth()
setSeconds()
setTime()
setUTCDate()
setUTCFullYear()
setUTCHours()
setUTCMilliseconds()
setUTCMinutes()
setUTCMonth()
setUTCSeconds()
toDateString()
toISOString()
toJSON()
toLocaleDateString()
toLocaleTimeString()
toLocaleString()
toString()
toTimeString()
toUTCString()
UTC()
valueOf()

JS Error
name
message

JS Global
decodeURI()
decodeURIComponent()
encodeURI()
encodeURIComponent()
escape()
eval()
Infinity
isFinite()
isNaN()
NaN
Number()
parseFloat()
parseInt()
String()
undefined
unescape()

JS JSON
parse()
stringify()

JS Math
abs()
acos()
acosh()
asin()
asinh()
atan()
atan2()
atanh()
cbrt()
ceil()
clz32()
cos()
cosh()
E
exp()
expm1()
floor()
fround()
LN2
LN10
log()
log10()
log1p()
log2()
LOG2E
LOG10E
max()
min()
PI
pow()
random()
round()
sign()
sin()
sqrt()
SQRT1_2
SQRT2
tan()
tanh()
trunc()

JS Number
constructor
isFinite()
isInteger()
isNaN()
isSafeInteger()
MAX_VALUE
MIN_VALUE
NEGATIVE_INFINITY
NaN
POSITIVE_INFINITY
prototype
toExponential()
toFixed()
toLocaleString()
toPrecision()
toString()
valueOf()

JS OperatorsJS RegExp
constructor
compile()
exec()
g
global
i
ignoreCase
lastIndex
m
multiline
n+
n*
n?
n{X}
n{X,Y}
n{X,}
n$
^n
?=n
?!n
source
test()
toString()

(x|y)
.
\w
\W
\d
\D
\s
\S
\b
\B
\0
\n
\f
\r
\t
\v
\xxx
\xdd
\uxxxx

JS Statements
break
class
continue
debugger
do…while
for
for…in
for…of
function
if…else
return
switch
throw
try…catch
var
while

JS String
charAt()
charCodeAt()
concat()
constructor
endsWith()
fromCharCode()
includes()
indexOf()
lastIndexOf()
length
localeCompare()
match()
prototype
repeat()
replace()
search()
slice()
split()
startsWith()
substr()
substring()
toLocaleLowerCase()
toLocaleUpperCase()
toLowerCase()
toString()
toUpperCase()
trim()
valueOf()

Число

Числовой тип данных () представляет как целочисленные значения, так и числа с плавающей точкой.

Существует множество операций для чисел, например, умножение , деление , сложение , вычитание и так далее.

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

  • представляет собой математическую ∞. Это особое значение, которое больше любого числа.

    Мы можем получить его в результате деления на ноль:

    Или задать его явно:

  • означает вычислительную ошибку. Это результат неправильной или неопределённой математической операции, например:

    Значение «прилипчиво». Любая операция с возвращает :

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

Математические операции – безопасны

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

Скрипт никогда не остановится с фатальной ошибкой (не «умрёт»). В худшем случае мы получим как результат выполнения.

Специальные числовые значения относятся к типу «число». Конечно, это не числа в привычном значении этого слова.

Подробнее о работе с числами мы поговорим в главе Числа.

Пустые элементы

Массивы JavaScript допускают пустые элементы. Массив ниже синтаксически верный и имеет длину 3 элемента:

const arr = ;

arr.length; // 3

Что еще более запутывает, так это то, что циклические конструкции трактуют иначе, чем . Ниже показано, как четыре циклических конструкции обрабатывают с пустым элементом. for/in и for/each пропускают пустой элемент, for и for/of — нет.

// Prints "a, undefined, c"
for (let i = 0; i < arr.length; ++i) {
  console.log(arr);
}

// Prints "a, c"
arr.forEach(v => console.log(v));

// Prints "a, c"
for (let i in arr) {
  console.log(arr);
}

// Prints "a, undefined, c"
for (const v of arr) {
  console.log(v);
}

Если вам интересно, все 4 конструкции выведут «a, undefined, c» для .

Есть еще один способ добавить пустой элемент в массив:

// Equivalent to ``
const arr = ;
arr = 'e';

forEach() и for/in пропускают пустые элементы в массиве, for и for/of — нет. Поведение forEach() может вызвать проблемы, однако можно заметить, что дыры в массивах JavaScript, как правило, встречаются редко, поскольку они не поддерживаются в JSON:

$ node
> JSON.parse('{"arr":}')
{ arr:  }
> JSON.parse('{"arr":}')
{ arr:  }
> JSON.parse('{"arr":}')
SyntaxError: Unexpected token , in JSON at position 12

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

Вывод: for/in и forEach() не реагируют на пустые элементы, также известные как «дыры», в массиве. Редко есть какая-либо причина рассматривать дыры как особый случай, а не рассматривать индекс как значение undefined. Если вы допускаете наличие дыр, ниже приведен пример файла .eslintrc.yml, который запрещает вызов forEach().

parserOptions:
  ecmaVersion: 2018
rules:
  no-restricted-syntax:
    - error
    - selector: CallExpression
      message: Do not use `forEach()`, use `for/of` instead

Async/Await и генераторы

Другой крайний случай с forEach() — это то, что он не совсем правильно работает с async/await или генераторами. Если ваш callback forEach() является синхронным, то это не имеет значения, но вы не сможете использовать await внутри callback forEach ():

async function run() {
  const arr = ;
  arr.forEach(el => {
    // SyntaxError
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  });
}

Вы также не сможете использовать yield:

function* run() {
  const arr = ;
  arr.forEach(el => {
    // SyntaxError
    yield new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  });
}

Но приведенные выше примеры отлично работают с for/of:

async function asyncFn() {
  const arr = ;
  for (const el of arr) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  }
}

function* generatorFn() {
  const arr = ;
  for (const el of arr) {
    yield new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  }
}

Даже если вы пометите свой callback forEach() как async, вам будет сложно заставить асинхронный метод forEach() работать последовательно. Например, приведенный ниже скрипт будет печатать 0-9 в обратном порядке.

async function print(n) {
  // Wait 1 second before printing 0, 0.9 seconds before printing 1, etc.
  await new Promise(resolve => setTimeout(() => resolve(), 1000 - n * 100));
  // Will usually print 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 but order is not strictly
  // guaranteed.
  console.log(n);
}

async function test() {
  .forEach(print);
}

test();

T

Вывод: если вы используете async/await или генераторы, помните, что forEach() является синтаксическим сахаром. Как сахар, его следует использовать экономно и не для всего.

Точка с запятой

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

Так тоже будет работать:

В этом случае JavaScript интерпретирует перенос строки как «неявную» точку с запятой. Это называется .

В большинстве случаев новая строка подразумевает точку с запятой. Но «в большинстве случаев» не значит «всегда»!

В некоторых ситуациях новая строка всё же не означает точку с запятой. Например:

Код выведет , потому что JavaScript не вставляет здесь точку с запятой. Интуитивно очевидно, что, если строка заканчивается знаком , значит, это «незавершённое выражение», поэтому точка с запятой не требуется. И в этом случае всё работает, как задумано.

Но есть ситуации, где JavaScript «забывает» вставить точку с запятой там, где она нужна.

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

Пример ошибки

Если вы хотите увидеть конкретный пример такой ошибки, обратите внимание на этот код:

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

А теперь добавим перед кодом и не поставим в конце точку с запятой:

Теперь, если запустить код, выведется только первый , а затем мы получим ошибку!

Всё исправится, если мы поставим точку с запятой после :

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

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

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

Мы рекомендуем ставить точку с запятой между инструкциями, даже если они отделены переносами строк. Это правило широко используется в сообществе разработчиков. Стоит отметить ещё раз – в большинстве случаев можно не ставить точку с запятой. Но безопаснее, особенно для новичка, ставить её.

Синтаксис

Синтаксис для объявления функции:

Функция создаётся с заданными аргументами и телом .

Это проще понять на конкретном примере. Здесь объявлена функция с двумя аргументами:

А вот функция без аргументов, в этом случае достаточно указать только тело:

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

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

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

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

The for Loop

The loop repeats a block of code as long as a certain condition is met. It is typically used to execute a block of code for certain number of times. Its syntax is:

for(initialization; condition; increment) {
    // Code to be executed
}

The parameters of the loop statement have following meanings:

  • initialization — it is used to initialize the counter variables, and evaluated once unconditionally before the first execution of the body of the loop.
  • condition — it is evaluated at the beginning of each iteration. If it evaluates to , the loop statements execute. If it evaluates to , the execution of the loop ends.
  • increment — it updates the loop counter with a new value each time the loop runs.

The following example defines a loop that starts with . The loop will continued until the value of variable is less than or equal to 5. The variable will increase by 1 each time the loop runs:

The loop is particularly useful for iterating over an array. The following example will show you how to print each item or element of the JavaScript array.

Цикл while

Синтаксис цикла while следующий:

Сначала объявляем переменную i, которая является счётчиком и внутри цикла мы этот счётчик инкрементируем. Внутри круглых скобок пишем условие входа/выхода из цикла.

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

Для примера выведем строку ‘Всем привет!’ 10 раз.

var i = 0;
while(i ');
    i++;
}

Переменная i, может начаться как с 0 так и с 1 или с другого любого числа.

Условие выхода является в тоже время и условием входа. Цикл работает следующим образом: Сначала проверяется если переменная i, меньше 10, и если условие истина, то мы входим в цикл, иначе, нет. В данном случае если переменная i будет равна 30, например, то цикл не выполнится, потому что 30 не меньше 10.

Зашли цикл, вывели строчку ‘Всем привет’, инкрементировали счётчик и опять переходим к условию, где опять проверяем если значение переменной i, меньше 10, то мы входим в цикл, иначе выходим из него. И так происходит до того момента когда условие входа станет лож, то есть значение переменной i будет 10. 10 не меньше 10, поэтому мы уже не входим в цикл, а идём дальше.

Замечание! Не забудьте инкрементировать счётчик (i++), иначе опять же получится бесконечный цикл.

С циклом while разобрались, теперь перейдём к циклу do-while.

Цикл for

Цикл for представляет собой конструкцию цикла, которая часто оказывается более удобной, чем цикл while. Цикл for упрощает конструирование циклов, следующих шаблону, общему для большинства циклов. Большинство циклов имеют некоторую переменную-счетчик. Эта переменная инициализируется перед началом цикла и проверяется перед каждой итерацией. Наконец, переменная-счетчик инкрементируется или изменяется каким-либо другим образом в конце тела цикла, непосредственно перед повторной проверкой переменной. Инициализация, проверка и обновление — это три ключевых операции, выполняемых с переменной цикла. Инструкция for делает эти три шага явной частью синтаксиса цикла:

for(инициализация; проверка; инкремент)
{
    инструкция
}

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

Проще всего объяснить работу цикла for, показав эквивалентный ему цикл while:

инициализация; 

while(проверка) 
{ 
    инструкция;
    инкремент;
}

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

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

Вывести числа от 0 до 9 можно также с помощью цикла for, как показано ниже, в противовес эквивалентному циклу while, показанному в примере ранее:

Конечно, циклы могут быть значительно более сложными, чем в этих простых примерах, и иногда в каждой итерации цикла изменяется несколько переменных. Эта ситуация — единственный случай в JavaScript, когда часто применяется оператор «запятая» — он позволяет объединить несколько выражений инициализации и инкрементирования в одно выражение, подходящее для использования в цикле for:

Инкремент/декремент

Одной из наиболее частых числовых операций является увеличение или уменьшение на единицу.

Для этого существуют даже специальные операторы:

  • Инкремент увеличивает переменную на 1:

  • Декремент уменьшает переменную на 1:

Важно:

Инкремент/декремент можно применить только к переменной. Попытка использовать его на значении, типа 5++, приведёт к ошибке.

Операторы и могут быть расположены не только после, но и до переменной.

  • Когда оператор идёт после переменной — это «постфиксная форма»: .
  • «Префиксная форма» — это когда оператор идёт перед переменной: .

Обе эти инструкции делают одно и то же: увеличивают на .

Есть ли разница между ними? Да, но увидеть её мы сможем, только если будем использовать значение, которое возвращают .

Давайте проясним этот момент. Как мы знаем, все операторы возвращают значение. Операторы инкремента/декремента не исключение. Префиксная форма возвращает новое значение, в то время как постфиксная форма возвращает старое (до увеличения/уменьшения числа).

Чтобы увидеть разницу, вот небольшой пример:

В строке префиксная форма увеличивает и возвращает новое значение . Так что покажет .

Теперь посмотрим на постфиксную форму:

В строке постфиксная форма также увеличивает , но возвращает старое значение (которое было до увеличения). Так что покажет .

Подведём итоги:

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

  • Если хочется тут же использовать результат, то нужна префиксная форма:

  • Если нужно увеличить и при этом получить значение переменной до увеличения – нужна постфиксная форма:

Инкремент/декремент можно использовать в любых выражениях

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

Например:

Сравните с:

Хотя технически здесь всё в порядке, такая запись обычно делает код менее читабельным. Одна строка выполняет множество действий – нехорошо.

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

Лучше использовать стиль «одна строка – одно действие»:

Цикл «while»

Цикл имеет следующий синтаксис:

Код из тела цикла выполняется, пока условие истинно.

Например, цикл ниже выводит , пока :

Одно выполнение тела цикла по-научному называется итерация. Цикл в примере выше совершает три итерации.

Если бы строка отсутствовала в примере выше, то цикл бы повторялся (в теории) вечно. На практике, конечно, браузер не позволит такому случиться, он предоставит пользователю возможность остановить «подвисший» скрипт, а JavaScript на стороне сервера придётся «убить» процесс.

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

Например, – более краткий вариант :

Фигурные скобки не требуются для тела цикла из одной строки

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

Цикл for

Синтаксис цикла :

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

  1. Первое выражение всегда вычисляется только один раз – перед первой итерацией. Поэтому обычно в качестве первого выражения выступает определение переменной, которая используется в условии выполнения цикла в качестве счётчика.
  2. Второе выражение определяет условие выполнения цикла. Оно вычисляется перед каждой итерацией и определяет, будет ли выполняться тело цикла. Если результатом вычисления выражения является истинное значение, программный код в теле цикла выполняется. Если возвращается ложное значение, выполнение цикла завершается и управление переходит к следующей после цикла инструкции. Если при первой проверке условия, оно оказывается ложным, код в теле цикла не выполнится ни разу.
  3. После каждой итерации вычисляется третье выражение. Обычно его используют для изменения значения переменной, которая используется в проверке условия выполнения цикла.

Пример цикла :

for (var count = 0; count < 5; count++)
  document.write(count + " ");

Попробовать »

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

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

var i = 0;
for (; i < 4; i++) ...

var i = 0;
for (; i < 4; ) ...

for (var i = 1; /* нет условия */ ; i++) ...

// Это эквивалентно следующему коду
for (var i = 1; true; i++) ...

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

// не выполнится, так как в проверке условия последнее выражение false
for (i = 1; i < 4, false; i++) ...

for (var i = 1, j = 5; i <= 5; i++, j--)
  document.write(i + " " + j +"<br>");

Попробовать »

Вложенные циклы

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

Пример вложенного цикла:

Выполнить код »
Скрыть результаты

Теперь попытаемся разобраться, как это работает. Первый (внешний) цикл после каждой итерации увеличивает значение переменной i, а второй (внутренний) – переменной j. За одну итерацию внешнего цикла внутренний выполняется девять раз. По условию (i внешний цикл выполнится 9 раз. Соответственно вложенный цикл будет выполнятся тоже 9 раз, а код внутри него – 9*9 итого 81 раз.

Иными словами, код читаем так: натыкаемся на внешний цикл, делаем первый проход, во время прохода натыкаемся на еще один цикл (внутренний), делаем девять проходов по нему, каждый раз выводя текущее значение переменной j. Выводим значение i и далее возвращаемся в начало внешнего цикла для второго прохода и так 9 раз.

Синтаксис цикла while

Синтаксис циклов for и while очень похож.

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

Вот, как должен выглядеть код:

while ()  
{  
// вставьте сюда код, который должен выполняться циклично  
}

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

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

Заключение

Как правило, for/of — это самый надежный способ перебора массива в JavaScript. Он более лаконичен, чем обычный цикл for, и не имеет такого количества граничных случаев, как for/in и forEach(). Основным недостатком for/of является то, что вам нужно проделать дополнительную работу для доступа к индексу массива (см. дополнение), и вы не можете строить цепочки кода, как вы можете это делать с помощью forEach(). Но если вы знаете все особенности forEach(), то во многих случаях его использование делает код более лаконичным.

Дополнение: Чтобы получить доступ к текущему индексу массива в цикле for/of, вы можете использовать функцию  .

for (const  of arr.entries()) {
  console.log(i, v); // Prints "0 a", "1 b", "2 c"
}

Оригинал: For vs forEach() vs for/in vs for/of in JavaScript

Spread the love

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector